0% found this document useful (0 votes)
56 views69 pages

2 1-Cabana-Xam1 4

This document discusses migrating the CabañasRD app from a native Android app to a cross-platform app using Xamarin Forms. The app will be rebuilt following a Clean Architecture template. Key steps include: 1) Designing the app structure and UI in XAML based on an Adobe XD design; 2) Implementing map, splash screens, and search functionality; 3) Moving the backend to Azure Functions and CosmosDB; 4) Adding continuous integration with App Center. The document provides code snippets to set up the initial project structure and main tabbed page UI in Xamarin Forms.

Uploaded by

hsuyip
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
56 views69 pages

2 1-Cabana-Xam1 4

This document discusses migrating the CabañasRD app from a native Android app to a cross-platform app using Xamarin Forms. The app will be rebuilt following a Clean Architecture template. Key steps include: 1) Designing the app structure and UI in XAML based on an Adobe XD design; 2) Implementing map, splash screens, and search functionality; 3) Moving the backend to Azure Functions and CosmosDB; 4) Adding continuous integration with App Center. The document provides code snippets to set up the initial project structure and main tabbed page UI in Xamarin Forms.

Uploaded by

hsuyip
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 69

CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

talkwithangel.com

CabañasRD, from native to cross-


platform with Xamarin Forms [1/4].
Angel Garcia

92-117 minutes

Hi folks, this time we are going to migrate a famous Dominican app


to Xamarin Forms, CabañasRD (+50,000 downloads in Play Store).

Right now the app is only available for Android, we will rebuild and
redefine the design with Xamarin Forms to have both platforms
(iOS and Android).

The plan is to divide all the process in 4 parts:

1. Taking our Adobe XD design to XAML: Design and structure (this


post).

2. Implementing the command for opening native map, app icons,


splash screens, info tab and also the search functionality over the
map.

3. Moving the data to the cloud: Let’s make our backend with Azure
Functions and CosmosDB and consume it from the app.

4. Implementing continuous integration and continuous delivery with


App Center.

From Adobe XD to XAML: Design and structure.

The current design of the app looks like this:

1 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

So, we are going to change it to this (made with Adobe XD):

For the app structure we are going to follow my recipe for a Clean
Architecture using this template which already has Prism as MVVM
framework, if you want to know more about making and consuming
your own templates see this post.

Let’s begin installing the template using the .NET Core CLI, to do
this we are going to download or clone this repository, open a
terminal in the root folder and run the following command:

dotnet new -i PrismCleanApp

Cool! now we are going to create our CabanasRD app based on


the installed template, you can leave the previous folder and run
this command:

dotnet new cleanxam -n CabanasRD -o CabanasRD

Go to the created project and open the solution file to start coding
our Xamarin app.

As the app is already published in the Play Store, we have to use


the same package name for Android and why not, we will use the

2 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

same for the bundle identifier (for iOS).

Open the Android manifest in App/CabanasRD.Android/Properties


/AndroidManifest.xml and change the package name to
com.cabanasrd.

Open the Info.plist in App/CabanasRD.iOS/Info.plist and change


the bundle identifier to com.cabanasrd.

We need to check one more thing before starting, the template


doesn’t have the versions of Xamarin.Forms and
Xamarin.Essentials that we are going to use:

Using the “Manage NuGet Packages” options in the solution,


update in all the App projects the following:

Xamarin.Forms ⇒ 4.7.0.1080

Xamarin.Essentials ⇒ 1.5.3.2

Now we are ready to create the first element we need, the


TabbedPage, which will be our main view.

Create a folder named “Main” in App/CabanasRD/UI, then inside


the new folder create two folders for our Views and ViewModels,

3 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

and then add a new Forms TabbedPage XAML named


“MainTabbedPage.xaml” in the View folder and also we need its
ViewModel called “MainTabbedPageViewModel.cs”, so your UI
folder should look like this:

Proceed to register this view in the Prism container in


App/CabanasRD/App.xaml.cs and use it as the main page in the
navigation:

using System;

using Prism;

using Prism.Ioc;

using Xamarin.Forms;

using Xamarin.Forms.Xaml;

using CabanasRD.UI.Main.Views;

using CabanasRD.UI.Main.ViewModels;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]

namespace CabanasRD

public partial class App

public App() : this(null) { }

public App(IPlatformInitializer initializer) : base(initializer) { }

4 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

protected override async void OnInitialized()

InitializeComponent();

await
NavigationService.NavigateAsync("NavigationPage/MainTabbedPage");

protected override void RegisterTypes(IContainerRegistry


containerRegistry)

containerRegistry.RegisterForNavigation<NavigationPage>();

containerRegistry.RegisterForNavigation<MainTabbedPage,
MainTabbedPageViewModel>();

If you look inside the App/CabanasRD/UI folder, the template


already had included a folder named “Orders” as boilerplate code,
delete this, we don’t need it anymore, also remove it from
Core/CabanasRD.Domain, Core/CabanasRD.UseCases and
Core/CabanasRD.Data.

Go back to the App/CabanasRD/UI/Main/Views


/MainTabbedPage.xaml and add the following for create the tabs
(also add some styles):

//In the MainTabbedPage.xaml

<?xml version="1.0" encoding="utf-8" ?>

<TabbedPage

x:Class="CabanasRD.UI.Main.Views.MainTabbedPage"

xmlns="http://xamarin.com/schemas/2014/forms"

5 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:android="clr-
namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
assembly=Xamarin.Forms.Core"

android:TabbedPage.ToolbarPlacement="Bottom"

NavigationPage.HasNavigationBar="False"

Style="{DynamicResource TabStyle}">

<TabbedPage.Children>

<ContentPage Title="Mapa" Visual="Material">

</ContentPage>

<ContentPage Title="Información" Visual="Material">

</ContentPage>

</TabbedPage.Children>

</TabbedPage>

//In the App.xaml

<Application.Resources>

....

<Style TargetType="NavigationPage">

<Setter Property="BarBackgroundColor" Value="#FDC830" />

<Setter Property="BarTextColor" Value="#FFFFFF" />

</Style>

<Style x:Key="TabStyle" TargetType="TabbedPage">

<Setter Property="BarBackgroundColor" Value="#FFFFFF" />

<Setter Property="SelectedTabColor" Value="#F59302" />

<Setter Property="UnselectedTabColor" Value="#95989A" />

</Style>

6 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

....

</Application.Resources>

The result is this:

One important thing here, the property


android:TabbedPage.ToolbarPlacement=”Bottom” in the
TabbedPage is a Platform-Specific feature for placing the tabs at
bottom in Android (iOS by default has the bottom placement).

For the icons, we are going to use a FontImageSource, follow the


docs about embedding custom Fonts, first create a folder for
placing the fonts (.ttf) in App/CabanasRD called “Resources” and
inside it create another one called “Fonts”, download the Font
Awesome files from here, copy the .ttf files to the Fonts folder (with
build action “EmbeddedResource”), and include these fonts in our
app:

//In the Assembly.cs

[assembly: ExportFont("fa-regular-400.ttf", Alias


= "Font-Awesome-Regular")]
[assembly: ExportFont("fa-solid-900.ttf", Alias =
"Font-Awesome-Solid")]
[assembly: ExportFont("fa-brands-400.ttf", Alias =
"Font-Awesome-Brands")]

Now we can use the unicode provided by Font Awesome for the
desired icons:

For the first tab ⇒ &#xf5a0;

For the second tab ⇒ &#xf05a;

With this clear, let’s add the icons to our MainTabbedPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>

7 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<TabbedPage

x:Class="CabanasRD.UI.Main.Views.MainTabbedPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:android="clr-
namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
assembly=Xamarin.Forms.Core"

android:TabbedPage.ToolbarPlacement="Bottom"

NavigationPage.HasNavigationBar="False"

Style="{DynamicResource TabStyle}">

<TabbedPage.Children>

<ContentPage Title="Mapa" Visual="Material">

<ContentPage.IconImageSource>

<FontImageSource FontFamily="Font-Awesome-Solid"
Glyph="&#xf5a0;" />

</ContentPage.IconImageSource>

</ContentPage>

<ContentPage Title="Información" Visual="Material">

<ContentPage.IconImageSource>

<FontImageSource FontFamily="Font-Awesome-Solid"
Glyph="&#xf05a;" />

</ContentPage.IconImageSource>

</ContentPage>

</TabbedPage.Children>

</TabbedPage>

Now the tabs are ready:

8 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

The next step will be set up the map, if you go back to the design
you notice that the map is black and white (has a style) and we
have orange custom pins, the Xamarin.Forms.Map control doesn’t
have this feature and we don’t want to waste time doing a custom
renderer just for this, so, we are going to use
Xamarin.Forms.GoogleMaps package (3.2.1) which extends the

9 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

functionality of the original Map from Xamarin.

Please, before continuing, go and check the necessary stuffs we


need for use the GoogleMaps control in the “Usage” section in the
project repo.

Once completed the map initializations, it’s time to use it in the


corresponding view, create a new folder structure for the Map in
App/CabanasRD/UI like this:

Open the “MotelsMapPage.xaml” and put the following:

<?xml version="1.0" encoding="UTF-8" ?>

<ContentPage

x:Class="CabanasRD.UI.Map.Views.MotelsMapPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:maps="clr-namespace:Xamarin.Forms.GoogleMaps;
assembly=Xamarin.Forms.GoogleMaps"

NavigationPage.HasNavigationBar="False">

<ContentPage.Content>

<Grid>

<maps:Map

x:Name="MotelsMap"

HorizontalOptions="FillAndExpand"

InitialCameraUpdate="18.7993522, -70.7474826, 7.5, 0, 0"

ItemsSource="{Binding Locations}"

10 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

MapType="Street"

VerticalOptions="FillAndExpand">

<maps:Map.ItemTemplate>

<DataTemplate>

<maps:Pin

Address="(Presione para ver detalles)"

Icon="{Binding Icon}"

Label="{Binding Motel.Name}"

Position="{Binding Position}" />

</DataTemplate>

</maps:Map.ItemTemplate>

</maps:Map>

</Grid>

</ContentPage.Content>

</ContentPage>

At the moment we don’t have the the Locations collection in the


ViewModel (we are going to add it later).

Now, add this as the content page for the first tab, so our
MainTabbedPage.xaml will look like this:

<?xml version="1.0" encoding="utf-8" ?>

<TabbedPage

x:Class="CabanasRD.UI.Main.Views.MainTabbedPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:android="clr-
namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
assembly=Xamarin.Forms.Core"

11 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

xmlns:mapViews="clr-namespace:CabanasRD.UI.Map.Views"

android:TabbedPage.ToolbarPlacement="Bottom"

NavigationPage.HasNavigationBar="False"

Style="{DynamicResource TabStyle}">

<TabbedPage.Children>

<mapViews:MotelsMapPage Title="Mapa" Visual="Material">

<mapViews:MotelsMapPage.IconImageSource>

<FontImageSource FontFamily="Font-Awesome-Solid"
Glyph="&#xf5a0;" />

</mapViews:MotelsMapPage.IconImageSource>

</mapViews:MotelsMapPage>

<ContentPage Title="Información" Visual="Material">

<ContentPage.IconImageSource>

<FontImageSource FontFamily="Font-Awesome-Solid"
Glyph="&#xf05a;" />

</ContentPage.IconImageSource>

</ContentPage>

</TabbedPage.Children>

</TabbedPage>

Register the new view in the container and …. Yessir! we have the
map view ready:

12 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Next step is the style of the map, go to this site, select and
download the “Silver Theme” JSON, add it to App/CabanasRD root
folder as “GoogleMapStyles.json”, and then add the following to the
code behind of our MotelsMapPage.xaml (thanks Rendy for this
post):

using System;

using System.Collections.Generic;

using System.Reflection;

13 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

using CabanasRD.UI.Map.ViewModels;

using Xamarin.Forms;

using Xamarin.Forms.GoogleMaps;

namespace CabanasRD.UI.Map.Views

public partial class MotelsMapPage : ContentPage

public MotelsMapPage()

InitializeComponent();

AddMapStyle();

void AddMapStyle()

var assembly = typeof(MotelsMapPage).GetTypeInfo().Assembly;

var stream =
assembly.GetManifestResourceStream($"CabanasRD.GoogleMapStyles.json

string styleFile;

using (var reader = new System.IO.StreamReader(stream))

styleFile = reader.ReadToEnd();

MotelsMap.MapStyle = MapStyle.FromJson(styleFile);

14 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

This is looking cool now:

15 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Before adding the Pins collection we need the custom pin image,
the map control we are using has a Bindable Property for this.

Add a “marker.png” in the Assets folder for the Android project, and
also add the marker image to the Resources folder for the iOS
project.

Time to make our app business logic, we are going to create the
following structure in the Core/CabanasRD.Domain layer:

public class Motel

public int Id { get; set; }

public double Longitude { get; set; }

public double Latitude { get; set; }

public string Name { get; set; }

public List<MotelImage> Images { get; set; }

public List<MotelService> Services { get; set; }

public List<MotelPhone> Phones { get; set; }

public class MotelImage

16 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

public string Url { get; set; }

public string Description { get; set; }

public class MotelPhone

public string Number { get; set; }

public class MotelService

public string Name { get; set; }

public double Price { get; set; }

public string Description { get; set; }

For the Core/CabanasRD.Data layer:

public interface IMotelsSource

Task<IReadOnlyList<Motel>> GetAll();

public class MotelsRepository

17 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

private IMotelsSource _motelsSource;

public MotelsRepository(IMotelsSource motelsSource)

_motelsSource = motelsSource;

public async Task<IReadOnlyList<Motel>> GetMotelsAsync()

return await _motelsSource.GetAll();

For the Core/CabanasRD.UseCases layer:

public class GetAllMotels

private MotelsRepository _motelsRepository;

public GetAllMotels(MotelsRepository motelsRepository)

_motelsRepository = motelsRepository;

public Task<IReadOnlyList<Motel>> Invoke()

return _motelsRepository.GetMotelsAsync();

18 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Now we have to implement the data source, in this first post will be
in memory list, let’s move to App/CabanasRD/Framework
/DataSources and create a “InMemoryMotelsSource.cs” with the
following content:

using System;

using System.Collections.Generic;

using System.Threading.Tasks;

using CabanasRD.Data.Motels;

using CabanasRD.Domain.Motels;

namespace CabanasRD.Framework.DataSources

public class InMemoryMotelsSource: IMotelsSource

public async Task<IReadOnlyList<Motel>> GetAll()

await Task.Delay(1000);

return new List<Motel>

new Motel{

Id = 2,

Latitude = 18.4895593,

Longitude = -69.8247819,

Name = "Motel Cuarto Frío",

Images = new List<MotelImage>{

19 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

new MotelImage {

Url = "https://c2.peakpx.com/wallpaper/604/823/790/castle-
fortress-defense-architecture-wallpaper-preview.jpg"

},

new MotelImage {

Url = "https://media.npr.org/assets/img/2015/08/15
/robin1-8aea039fd0710bd0c8549c19abab4085e0e3024c-s800-
c85.jpg"

},

new MotelImage {

Url = "https://c2.peakpx.com/wallpaper/604/823/790/castle-
fortress-defense-architecture-wallpaper-preview.jpg"

},

new MotelImage {

Url = "https://media.npr.org/assets/img/2015/08/15
/robin1-8aea039fd0710bd0c8549c19abab4085e0e3024c-s800-
c85.jpg"

},

new MotelImage {

Url = "https://c2.peakpx.com/wallpaper/604/823/790/castle-
fortress-defense-architecture-wallpaper-preview.jpg"

},

new MotelImage {

Url = "https://media.npr.org/assets/img/2015/08/15
/robin1-8aea039fd0710bd0c8549c19abab4085e0e3024c-s800-
c85.jpg"

},

Phones = new List<MotelPhone>{

20 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

new MotelPhone{ Number = "809-000-0000"},

new MotelPhone{ Number = "829-000-0000"},

new MotelPhone{ Number = "849-000-0000"}

},

Services = new List<MotelService>{

new MotelService {

Name = "Normal",

Price = 725.00

},

new MotelService {

Name = "Ejecutiva",

Price = 925.00

},

new MotelService {

Name = "Vice Presidencial",

Price = 1050.00

},

new MotelService {

Name = "Presidencial",

Price = 1350.00

};

21 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Register all the necessary dependencies for the use case in the
App.xaml.cs:

using System;

using Prism;

using Prism.Ioc;

using Xamarin.Forms;

using Xamarin.Forms.Xaml;

using CabanasRD.Framework.DataSources;

using CabanasRD.UI.Map.Views;

using CabanasRD.UI.Map.ViewModels;

using CabanasRD.Data.Motels;

using CabanasRD.UseCases.Motels;

using CabanasRD.UI.Main.Views;

using CabanasRD.UI.Main.ViewModels;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]

namespace CabanasRD

public partial class App

public App() : this(null) { }

public App(IPlatformInitializer initializer) : base(initializer) { }

protected override async void OnInitialized()

InitializeComponent();

22 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

await
NavigationService.NavigateAsync("NavigationPage/MainTabbedPage");

protected override void RegisterTypes(IContainerRegistry


containerRegistry)

//Views

containerRegistry.RegisterForNavigation<NavigationPage>();

containerRegistry.RegisterForNavigation<MainTabbedPage,
MainTabbedPageViewModel>();

containerRegistry.RegisterForNavigation<MotelsMapPage,
MotelsMapPageViewModel>();

//Repositories & Data sources

containerRegistry.Register<MotelsRepository>();

containerRegistry.Register<IMotelsSource, InMemoryMotelsSource>();

//Use cases

containerRegistry.Register<GetAllMotels>();

For the map pins position and image we are going to use a DTO (or
model in the UI), place a file called “MotelLocation.cs” in
App/CabanasRD/UI/Map/Models:

using System;

using CabanasRD.Domain.Motels;

using Xamarin.Forms;

using Xamarin.Forms.GoogleMaps;

23 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

namespace CabanasRD.UI.Map.Models

public class MotelLocation

public Motel Motel { get; set; }

public Position Position { get; set; }

public BitmapDescriptor Icon { get; set; }

Now let’s place the UI logic for the map in the


MotelsMapPageViewModel.cs:

using System;

using System.Collections.ObjectModel;

using System.Linq;

using CabanasRD.UI.Map.Models;

using CabanasRD.UI.ViewModels;

using CabanasRD.UseCases.Motels;

using Prism.Commands;

using Prism.Navigation;

using Xamarin.Forms.GoogleMaps;

namespace CabanasRD.UI.Map.ViewModels

public class MotelsMapPageViewModel : ViewModelBase

public ObservableCollection<MotelLocation> Locations { get;


set; }

24 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

private readonly GetAllMotels _getAllMotels;

private readonly string markerIcon = "marker.png";

public MotelsMapPageViewModel(INavigationService
navigationService, GetAllMotels getAllMotels)

: base(navigationService)

_getAllMotels = getAllMotels;

Title = "Cabañas";

Locations = new ObservableCollection<MotelLocation>();

LoadMotelsLocations();

private async void LoadMotelsLocations()

foreach (var item in await _getAllMotels.Invoke())

//TODO: [Enhancement] Map from Domain to UI Model using


AutoMapper

Locations.Add(new MotelLocation {

Position = new Position(item.Latitude, item.Longitude),

Motel = item,

Icon = BitmapDescriptorFactory.FromBundle(markerIcon)

});

25 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Let’s see how it looks in the map:

26 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Now we need to implement the “InfoWindowClicked” event in the


map, to do this we have to add the event handler in the
MotelsMapPage.xaml (and its code behind) which will just act as a
bridge for calling the real implementation of this event in the
MotelsMapPageViewModel, we don’t have any Bindable
Property to do this directly in the view model.

//In MotelsMapPage.xaml

<maps:Map

...

InfoWindowClicked="InfoWindowClicked"

....

</maps:Map>

//In MotelsMapPage.xaml.cs

void InfoWindowClicked(System.Object sender,


Xamarin.Forms.GoogleMaps.InfoWindowClickedEventArgs e)

((MotelsMapPageViewModel)this.BindingContext).InfoWindowSelected(e.Pin);

//In the MotelsMapPageViewModel.cs

public void InfoWindowSelected(Pin pin) {

var motelDetails = Locations.FirstOrDefault(l => l.Motel.Name == pin.Label &&


l.Position.Equals(pin.Position));

var navParams = new NavigationParameters();

navParams.Add("MotelDetails", motelDetails.Motel);

NavigationService.NavigateAsync("MotelDetailsPage", navParams);

27 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

As you can see, we have now the logic to navigate to the motel
details page, let’s create this view (this is the funny part), we need
to create a MotelDetailsPage.xaml (under Views),
MotelDetailsPageViewModel.cs (under ViewModels) in
App/CabanasRD/UI/Map, and then register the new view in the
App.xaml.cs.

Before going to the XAML’s part, let’s put the following code in our
MotelDetailsPageViewModel:

using System;

using System.Collections.ObjectModel;

using CabanasRD.Domain.Motels;

using CabanasRD.UI.ViewModels;

using Prism.Navigation;

namespace CabanasRD.UI.Map.ViewModels

public class MotelDetailsPageViewModel : ViewModelBase

public Motel Motel { get; set; }

public ObservableCollection<MotelImage> Images { get; set; }

public ObservableCollection<MotelPhone> Phones { get; set; }

public ObservableCollection<MotelService> Services { get; set;


}

private string name;

public string Name

get { return name; }

set { SetProperty(ref name, value); }

28 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

private string distance;

public string Distance

get { return distance; }

set { SetProperty(ref distance, value); }

public MotelDetailsPageViewModel(INavigationService
navigationService)

: base(navigationService)

Images = new ObservableCollection<MotelImage>();

Services = new ObservableCollection<MotelService>();

Phones = new ObservableCollection<MotelPhone>();

public override void OnNavigatedTo(INavigationParameters


parameters)

base.OnNavigatedTo(parameters);

Motel = parameters.GetValue<Motel>("MotelDetails");

Name = Motel.Name;

//TODO: Get the current user's position and calculate distance

Distance = "1.2 KMs";

foreach (var item in Motel.Images)

Images.Add(item);

foreach (var item in Motel.Phones)

29 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Phones.Add(item);

foreach (var item in Motel.Services)

Services.Add(item);

Cool, now that we have the blank view connected with its view
model, let’s start defining the main container as a Grid with 5 rows
with the following definitions:

30 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<?xml version="1.0" encoding="UTF-8" ?>

<ContentPage

x:Class="CabanasRD.UI.Map.Views.MotelDetailsPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

BackgroundColor="#FFFFFF">

<ContentPage.Content>

<ScrollView>

<Grid RowSpacing="10">

<Grid.RowDefinitions>

<RowDefinition Height="219" />

<RowDefinition Height="296" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

</Grid>

</ScrollView>

</ContentPage.Content>

31 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

</ContentPage>

For the gradients and the rounded layouts, we are going to use the
awesome PancakeView version 1.4.2, follow the setup before
continuing.

For some elements style, like the button in the header, we are
going to use Xamarin.Forms.Visual.Material, follow the setup before
continuing.

Cool, now let’s use a PancakeView as container for the first row
(header) inside the Grid we defined before:

Add the following namespace to the ContentPage:

xmlns:yummy="clr-
namespace:Xamarin.Forms.PancakeView;
assembly=Xamarin.Forms.PancakeView"

So far, this is the result for the MotelDetailsPage:

32 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

We need to remove the Navigation Page Separator in iOS, I tried to


use this Platform Specific but it didn’t work, so let’s override this
property globally in the AppDelegate.cs (App/CabanasRD.iOS), put
this line before the LoadApplication call in the FinishedLaunching
method:

UINavigationBar.Appearance.ShadowImage = new
UIImage();

let’s see:

33 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

For making the bottom shape for this row, we are going to use a
SVG with the desired form (extracted from the Adobe XD design)
and place it vertically at the end.

Follow this blog post for setting up


Xamarin.FFImageLoading.Svg.Forms, this library allows us to use
SVG images in Xamarin Forms.

Create a folder in App/CabanasRD/Resources named “Images”


and place there the SVG file (header_shape.svg) with build action
EmbeddedResource, then add the image in the row 0, the
MotelDeatilsPage.xaml would be like this:

<?xml version="1.0" encoding="UTF-8" ?>

<ContentPage

34 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

x:Class="CabanasRD.UI.Map.Views.MotelDetailsPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;
assembly=FFImageLoading.Svg.Forms"

xmlns:ios="clr-
namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;
assembly=Xamarin.Forms.Core"

xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;
assembly=Xamarin.Forms.PancakeView"

ios:NavigationPage.HideNavigationBarSeparator="true"

BackgroundColor="#FFFFFF">

<ContentPage.Content>

<ScrollView>

<Grid RowSpacing="10">

<Grid.RowDefinitions>

<RowDefinition Height="219" />

<RowDefinition Height="296" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<!-- HEADER LAYOUT -->

<yummy:PancakeView

Grid.Row="0"

BackgroundGradientEndColor="#F37335"

BackgroundGradientStartColor="#FDC830"

35 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

HorizontalOptions="FillAndExpand" />

<ffimageloadingsvg:SvgCachedImage

Grid.Row="0"

Margin="0"

Aspect="AspectFit"

Source="resource://CabanasRD.Resources.Images.header_shape.svg"

VerticalOptions="End" />

<!-- END OF HEADER LAYOUT -->

</Grid>

</ScrollView>

</ContentPage.Content>

</ContentPage>

The result is this beautiful shape effect:

36 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Now, for putting the header elements we are going to use some
nested grids, so, first let’s define a grid like this:

We need two new fonts here:

Roboto-Light

Roboto-Bold

Define the styles for the “Title” (row 0), “Subtitle” (row 1),
“CreditCardAllowedIcon” (row 2, column 0), “HeaderIndication” (row
2, column 0) and “SeeRouteButton” (row 2, column 1):

37 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Cool, let’s make the grid, then put the “Title” and “Subtitle” labels
inside it with the following code:

<?xml version="1.0" encoding="UTF-8" ?>

<ContentPage

x:Class="CabanasRD.UI.Map.Views.MotelDetailsPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;
assembly=FFImageLoading.Svg.Forms"

xmlns:ios="clr-
namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;
assembly=Xamarin.Forms.Core"

xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;
assembly=Xamarin.Forms.PancakeView"

ios:NavigationPage.HideNavigationBarSeparator="true"

BackgroundColor="#FFFFFF">

<ContentPage.Content>

<ScrollView>

<Grid RowSpacing="10">

<Grid.RowDefinitions>

<RowDefinition Height="219" />

<RowDefinition Height="296" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<!-- HEADER LAYOUT -->

38 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<yummy:PancakeView

Grid.Row="0"

BackgroundGradientEndColor="#F37335"

BackgroundGradientStartColor="#FDC830"

HorizontalOptions="FillAndExpand">

<Grid Padding="20">

<Grid.RowDefinitions>

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" />

<ColumnDefinition Width="140" />

</Grid.ColumnDefinitions>

<Label

Grid.Row="0"

Grid.ColumnSpan="2"

Style="{DynamicResource Title}"

Text="{Binding Name}" />

<Label

Grid.Row="1"

Grid.ColumnSpan="2"

Style="{DynamicResource Subtitle}"

Text="{Binding Distance}" />

</Grid>

39 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

</yummy:PancakeView>

<ffimageloadingsvg:SvgCachedImage

Grid.Row="0"

Margin="0"

Aspect="AspectFit"

Source="resource://CabanasRD.Resources.Images.header_shape.svg"

VerticalOptions="End" />

<!-- END OF HEADER LAYOUT -->

</Grid>

</ScrollView>

</ContentPage.Content>

</ContentPage>

Looking good :

40 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

To achieve the transparent chip effect (row 2 column 0) we will use


another grid just for absolute positioning, this is because we don’t
want the transparency in the label, just in the “background” :

And, for the button we are going to add a negative margin in the
right to make the effect of “only left right rounded”, export the icon
needed and put it in the row 2 column 1 with the “SeeRoute” style.

So, our XAML will be like this:

<?xml version="1.0" encoding="UTF-8" ?>

<ContentPage

x:Class="CabanasRD.UI.Map.Views.MotelDetailsPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;
assembly=FFImageLoading.Svg.Forms"

41 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

xmlns:ios="clr-
namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;
assembly=Xamarin.Forms.Core"

xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;
assembly=Xamarin.Forms.PancakeView"

ios:NavigationPage.HideNavigationBarSeparator="true"

BackgroundColor="#FFFFFF">

<ContentPage.Content>

<ScrollView>

<Grid RowSpacing="10">

<Grid.RowDefinitions>

<RowDefinition Height="219" />

<RowDefinition Height="296" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<!-- HEADER LAYOUT -->

<yummy:PancakeView

Grid.Row="0"

BackgroundGradientEndColor="#F37335"

BackgroundGradientStartColor="#FDC830"

HorizontalOptions="FillAndExpand">

<Grid Padding="20">

<Grid.RowDefinitions>

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

42 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" />

<ColumnDefinition Width="140" />

</Grid.ColumnDefinitions>

<Label

Grid.Row="0"

Grid.ColumnSpan="2"

Style="{DynamicResource Title}"

Text="{Binding Name}" />

<Label

Grid.Row="1"

Grid.ColumnSpan="2"

Style="{DynamicResource Subtitle}"

Text="{Binding Distance}" />

<Grid Grid.Row="2" Grid.Column="0">

<Label

Margin="10,0"

Style="{DynamicResource CreditCardAllowedIcon}"

Text="&#xf09d;"

VerticalTextAlignment="Center" />

<Label

Margin="28,0,10,0"

Style="{DynamicResource HeaderIndication}"

Text="Acepta Tarjetas de Crédito"

43 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

VerticalTextAlignment="Center" />

<yummy:PancakeView

BackgroundColor="#000000"

CornerRadius="16"

HeightRequest="38"

HorizontalOptions="Start"

Opacity="0.07"

VerticalOptions="Center"

WidthRequest="180" />

</Grid>

<Button

Grid.Row="2"

Grid.Column="1"

Margin="0,0,-30,0"

ImageSource="route"

Style="{DynamicResource SeeRoute}"

Text="Ver Ruta" />

</Grid>

</yummy:PancakeView>

<ffimageloadingsvg:SvgCachedImage

Grid.Row="0"

Margin="0"

Aspect="AspectFit"

Source="resource://CabanasRD.Resources.Images.header_shape.svg"

VerticalOptions="End" />

<!-- END OF HEADER LAYOUT -->

44 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

</Grid>

</ScrollView>

</ContentPage.Content>

</ContentPage>

we have our header ready!

45 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Time for the pics (row 1 and 2 of our main grid), the “Pics Layout”
will be a CarouselView with an IndicatorView, see the docs for
initialization setup.

For the rounded corners of the images we need to clip the image to
a PancakeView and use this as the DataTemplate of our
CarouselView, the ItemsSource it’s already defined in the
ViewModel as an ObservableCollection.

Our MotelDetailsPage.xaml will look like this:

<?xml version="1.0" encoding="UTF-8" ?>

<ContentPage

x:Class="CabanasRD.UI.Map.Views.MotelDetailsPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;
assembly=FFImageLoading.Svg.Forms"

46 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

xmlns:ios="clr-
namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;
assembly=Xamarin.Forms.Core"

xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;
assembly=Xamarin.Forms.PancakeView"

ios:NavigationPage.HideNavigationBarSeparator="true"

BackgroundColor="#FFFFFF">

<ContentPage.Content>

<ScrollView>

<Grid RowSpacing="10">

<Grid.RowDefinitions>

<RowDefinition Height="219" />

<RowDefinition Height="296" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<!-- HEADER LAYOUT -->

<yummy:PancakeView

Grid.Row="0"

BackgroundGradientEndColor="#F37335"

BackgroundGradientStartColor="#FDC830"

HorizontalOptions="FillAndExpand">

<Grid Padding="20">

<Grid.RowDefinitions>

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

47 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" />

<ColumnDefinition Width="140" />

</Grid.ColumnDefinitions>

<Label

Grid.Row="0"

Grid.ColumnSpan="2"

Style="{DynamicResource Title}"

Text="{Binding Name}" />

<Label

Grid.Row="1"

Grid.ColumnSpan="2"

Style="{DynamicResource Subtitle}"

Text="{Binding Distance}" />

<Grid Grid.Row="2" Grid.Column="0">

<Label

Margin="10,0"

Style="{DynamicResource CreditCardAllowedIcon}"

Text="&#xf09d;"

VerticalTextAlignment="Center" />

<Label

Margin="28,0,10,0"

Style="{DynamicResource HeaderIndication}"

Text="Acepta Tarjetas de Crédito"

48 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

VerticalTextAlignment="Center" />

<yummy:PancakeView

BackgroundColor="#000000"

CornerRadius="16"

HeightRequest="38"

HorizontalOptions="Start"

Opacity="0.07"

VerticalOptions="Center"

WidthRequest="180" />

</Grid>

<Button

Grid.Row="2"

Grid.Column="1"

Margin="0,0,-30,0"

ImageSource="route"

Style="{DynamicResource SeeRoute}"

Text="Ver Ruta" />

</Grid>

</yummy:PancakeView>

<ffimageloadingsvg:SvgCachedImage

Grid.Row="0"

Margin="0"

Aspect="AspectFit"

Source="resource://CabanasRD.Resources.Images.header_shape.svg"

VerticalOptions="End" />

<!-- END OF HEADER LAYOUT -->

49 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<!-- PICS LAYOUT -->

<CarouselView

Grid.Row="1"

Margin="20,10"

IndicatorView="indicatorView"

ItemsSource="{Binding Images}">

<CarouselView.ItemTemplate>

<DataTemplate>

<yummy:PancakeView

Margin="0"

Padding="0"

BackgroundColor="LightGray"

CornerRadius="30"

IsClippedToBounds="True">

<Image

Aspect="AspectFill"

HorizontalOptions="FillAndExpand"

Source="{Binding Url}"

VerticalOptions="FillAndExpand" />

</yummy:PancakeView>

</DataTemplate>

</CarouselView.ItemTemplate>

</CarouselView>

<IndicatorView

x:Name="indicatorView"

Grid.Row="2"

50 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

IndicatorColor="LightGray"

SelectedIndicatorColor="DarkGray" />

<!-- END OF PICS LAYOUT -->

</Grid>

</ScrollView>

</ContentPage.Content>

</ContentPage>

So far we got this:

51 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Cool, let’s make the row 3 of our main grid, the “Services Layout”,
before going to our view, add the following styles:

<Style x:Key="CardTitle" TargetType="Label">

<Setter Property="FontFamily" Value="Roboto-Bold" />

<Setter Property="FontSize" Value="27" />

<Setter Property="TextColor" Value="#FFFFFF" />

</Style>

<Style x:Key="CardBoldDetail" TargetType="Label">

<Setter Property="FontSize" Value="20" />

<Setter Property="FontFamily" Value="Roboto-Bold" />

<Setter Property="TextColor" Value="#FFFFFF" />

</Style>

<Style x:Key="CardDetail" TargetType="Label">

<Setter Property="TextColor" Value="#FFFFFF" />

<Setter Property="FontSize" Value="20" />

<Setter Property="LineBreakMode" Value="TailTruncation" />

</Style>

We want to do something like this:

52 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Go back to the MotelDetailsPage.xaml and add the “Services


Layout”:

<?xml version="1.0" encoding="UTF-8" ?>

<ContentPage

x:Class="CabanasRD.UI.Map.Views.MotelDetailsPage"

xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;
assembly=FFImageLoading.Svg.Forms"

xmlns:ios="clr-
namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;
assembly=Xamarin.Forms.Core"

xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;
assembly=Xamarin.Forms.PancakeView"

ios:NavigationPage.HideNavigationBarSeparator="true"

BackgroundColor="#FFFFFF">

<ContentPage.Content>

<ScrollView>

<Grid RowSpacing="10">

<Grid.RowDefinitions>

<RowDefinition Height="219" />

<RowDefinition Height="296" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

53 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

</Grid.RowDefinitions>

<!-- HEADER LAYOUT -->

<yummy:PancakeView

Grid.Row="0"

BackgroundGradientEndColor="#F37335"

BackgroundGradientStartColor="#FDC830"

HorizontalOptions="FillAndExpand">

<Grid Padding="20">

<Grid.RowDefinitions>

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" />

<ColumnDefinition Width="140" />

</Grid.ColumnDefinitions>

<Label

Grid.Row="0"

Grid.ColumnSpan="2"

Style="{DynamicResource Title}"

Text="{Binding Name}" />

<Label

Grid.Row="1"

Grid.ColumnSpan="2"

Style="{DynamicResource Subtitle}"

54 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Text="{Binding Distance}" />

<Grid Grid.Row="2" Grid.Column="0">

<Label

Margin="10,0"

Style="{DynamicResource CreditCardAllowedIcon}"

Text="&#xf09d;"

VerticalTextAlignment="Center" />

<Label

Margin="28,0,10,0"

Style="{DynamicResource HeaderIndication}"

Text="Acepta Tarjetas de Crédito"

VerticalTextAlignment="Center" />

<yummy:PancakeView

BackgroundColor="#000000"

CornerRadius="16"

HeightRequest="38"

HorizontalOptions="Start"

Opacity="0.07"

VerticalOptions="Center"

WidthRequest="180" />

</Grid>

<Button

Grid.Row="2"

Grid.Column="1"

Margin="0,0,-30,0"

ImageSource="route"

55 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Style="{DynamicResource SeeRoute}"

Text="Ver Ruta" />

</Grid>

</yummy:PancakeView>

<ffimageloadingsvg:SvgCachedImage

Grid.Row="0"

Margin="0"

Aspect="AspectFit"

Source="resource://CabanasRD.Resources.Images.header_shape.svg"

VerticalOptions="End" />

<!-- END OF HEADER LAYOUT -->

<!-- PICS LAYOUT -->

<CarouselView

Grid.Row="1"

Margin="20,10"

IndicatorView="indicatorView"

ItemsSource="{Binding Images}">

<CarouselView.ItemTemplate>

<DataTemplate>

<yummy:PancakeView

Margin="0"

Padding="0"

BackgroundColor="LightGray"

CornerRadius="30"

IsClippedToBounds="True">

<Image

56 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Aspect="AspectFill"

HorizontalOptions="FillAndExpand"

Source="{Binding Url}"

VerticalOptions="FillAndExpand" />

</yummy:PancakeView>

</DataTemplate>

</CarouselView.ItemTemplate>

</CarouselView>

<IndicatorView

x:Name="indicatorView"

Grid.Row="2"

IndicatorColor="LightGray"

SelectedIndicatorColor="DarkGray" />

<!-- END OF PICS LAYOUT -->

<!-- SERVICES LAYOUT -->

<yummy:PancakeView

Grid.Row="3"

Margin="20,10"

Padding="20,20,20,30"

BackgroundGradientEndColor="#EEA849"

BackgroundGradientStartColor="#F46B45"

CornerRadius="20"

HorizontalOptions="FillAndExpand">

<StackLayout>

<Label

Margin="0,0,0,20"

57 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Style="{DynamicResource CardTitle}"

Text="Precios" />

<StackLayout BindableLayout.ItemsSource="{Binding Services,


Mode=OneTime}" HorizontalOptions="Start">

<BindableLayout.ItemTemplate>

<DataTemplate>

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" />

<ColumnDefinition Width="160" />

</Grid.ColumnDefinitions>

<Label

Grid.Column="0"

Style="{DynamicResource CardDetail}"

Text="{Binding Name}"

VerticalOptions="Center" />

<Label

Grid.Column="1"

HorizontalOptions="Center"

Style="{DynamicResource CardBoldDetail}"

TextColor="#FFFFFF"

VerticalOptions="Center">

<Label.FormattedText>

<FormattedString>

<Span Text="RD$ " />

<Span Text="{Binding Price, StringFormat='{0:N2}'}" />

58 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

</FormattedString>

</Label.FormattedText>

</Label>

<yummy:PancakeView

Grid.Column="1"

BackgroundColor="#000000"

CornerRadius="16"

HeightRequest="32"

Opacity="0.07" />

</Grid>

</DataTemplate>

</BindableLayout.ItemTemplate>

</StackLayout>

</StackLayout>

</yummy:PancakeView>

<!-- END OF SERVICES LAYOUT -->

</Grid>

</ScrollView>

</ContentPage.Content>

</ContentPage>

Run the app and go to the motel details, scroll down, you see
this now:

59 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

For the last row, the “Phones Layout”, we are going to do almost
the same, but the gradients colors and the grid inside the
BindableLayout will be different.

Finally, the XAML is going to look like this:

<?xml version="1.0" encoding="UTF-8" ?>

<ContentPage

x:Class="CabanasRD.UI.Map.Views.MotelDetailsPage"

xmlns="http://xamarin.com/schemas/2014/forms"

60 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;
assembly=FFImageLoading.Svg.Forms"

xmlns:ios="clr-
namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;
assembly=Xamarin.Forms.Core"

xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;
assembly=Xamarin.Forms.PancakeView"

ios:NavigationPage.HideNavigationBarSeparator="true"

BackgroundColor="#FFFFFF">

<ContentPage.Content>

<ScrollView>

<Grid RowSpacing="10">

<Grid.RowDefinitions>

<RowDefinition Height="219" />

<RowDefinition Height="296" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<!-- HEADER LAYOUT -->

<yummy:PancakeView

Grid.Row="0"

BackgroundGradientEndColor="#F37335"

BackgroundGradientStartColor="#FDC830"

HorizontalOptions="FillAndExpand">

<Grid Padding="20">

61 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<Grid.RowDefinitions>

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

<RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" />

<ColumnDefinition Width="140" />

</Grid.ColumnDefinitions>

<Label

Grid.Row="0"

Grid.ColumnSpan="2"

Style="{DynamicResource Title}"

Text="{Binding Name}" />

<Label

Grid.Row="1"

Grid.ColumnSpan="2"

Style="{DynamicResource Subtitle}"

Text="{Binding Distance}" />

<Grid Grid.Row="2" Grid.Column="0">

<Label

Margin="10,0"

Style="{DynamicResource CreditCardAllowedIcon}"

Text="&#xf09d;"

VerticalTextAlignment="Center" />

<Label

62 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Margin="28,0,10,0"

Style="{DynamicResource HeaderIndication}"

Text="Acepta Tarjetas de Crédito"

VerticalTextAlignment="Center" />

<yummy:PancakeView

BackgroundColor="#000000"

CornerRadius="16"

HeightRequest="38"

HorizontalOptions="Start"

Opacity="0.07"

VerticalOptions="Center"

WidthRequest="180" />

</Grid>

<Button

Grid.Row="2"

Grid.Column="1"

Margin="0,0,-30,0"

ImageSource="route"

Style="{DynamicResource SeeRoute}"

Text="Ver Ruta" />

</Grid>

</yummy:PancakeView>

<ffimageloadingsvg:SvgCachedImage

Grid.Row="0"

Margin="0"

Aspect="AspectFit"

63 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Source="resource://CabanasRD.Resources.Images.header_shape.svg"

VerticalOptions="End" />

<!-- END OF HEADER LAYOUT -->

<!-- PICS LAYOUT -->

<CarouselView

Grid.Row="1"

Margin="20,10"

IndicatorView="indicatorView"

ItemsSource="{Binding Images}">

<CarouselView.ItemTemplate>

<DataTemplate>

<yummy:PancakeView

Margin="0"

Padding="0"

BackgroundColor="LightGray"

CornerRadius="30"

IsClippedToBounds="True">

<Image

Aspect="AspectFill"

HorizontalOptions="FillAndExpand"

Source="{Binding Url}"

VerticalOptions="FillAndExpand" />

</yummy:PancakeView>

</DataTemplate>

</CarouselView.ItemTemplate>

</CarouselView>

64 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<IndicatorView

x:Name="indicatorView"

Grid.Row="2"

IndicatorColor="LightGray"

SelectedIndicatorColor="DarkGray" />

<!-- END OF PICS LAYOUT -->

<!-- SERVICES LAYOUT -->

<yummy:PancakeView

Grid.Row="3"

Margin="20,10"

Padding="20,20,20,30"

BackgroundGradientEndColor="#EEA849"

BackgroundGradientStartColor="#F46B45"

CornerRadius="20"

HorizontalOptions="FillAndExpand">

<StackLayout>

<Label

Margin="0,0,0,20"

Style="{DynamicResource CardTitle}"

Text="Precios" />

<StackLayout BindableLayout.ItemsSource="{Binding Services,


Mode=OneTime}" HorizontalOptions="Start">

<BindableLayout.ItemTemplate>

<DataTemplate>

<Grid>

<Grid.ColumnDefinitions>

65 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<ColumnDefinition Width="*" />

<ColumnDefinition Width="160" />

</Grid.ColumnDefinitions>

<Label

Grid.Column="0"

Style="{DynamicResource CardDetail}"

Text="{Binding Name}"

VerticalOptions="Center" />

<Label

Grid.Column="1"

HorizontalOptions="Center"

Style="{DynamicResource CardBoldDetail}"

TextColor="#FFFFFF"

VerticalOptions="Center">

<Label.FormattedText>

<FormattedString>

<Span Text="RD$ " />

<Span Text="{Binding Price, StringFormat='{0:N2}'}" />

</FormattedString>

</Label.FormattedText>

</Label>

<yummy:PancakeView

Grid.Column="1"

BackgroundColor="#000000"

CornerRadius="16"

HeightRequest="32"

66 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

Opacity="0.07" />

</Grid>

</DataTemplate>

</BindableLayout.ItemTemplate>

</StackLayout>

</StackLayout>

</yummy:PancakeView>

<!-- END OF SERVICES LAYOUT -->

<!-- PHONES LAYOUT -->

<yummy:PancakeView

Grid.Row="4"

Margin="20,10"

Padding="20,20,20,30"

BackgroundGradientEndColor="#FFE374"

BackgroundGradientStartColor="#F59302"

CornerRadius="20"

HorizontalOptions="FillAndExpand">

<StackLayout>

<Label

Margin="0,0,0,20"

Style="{DynamicResource CardTitle}"

Text="Teléfonos" />

<StackLayout BindableLayout.ItemsSource="{Binding Phones}"


HorizontalOptions="Start">

<BindableLayout.ItemTemplate>

<DataTemplate>

67 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

<Grid>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="Auto" />

</Grid.ColumnDefinitions>

<Label

Grid.Column="0"

Margin="10,0"

HorizontalOptions="Center"

Style="{DynamicResource CardBoldDetail}"

Text="{Binding Number}"

TextColor="#FFFFFF"

VerticalOptions="Center" />

<yummy:PancakeView

Grid.Column="0"

BackgroundColor="#000000"

CornerRadius="16"

HeightRequest="32"

Opacity="0.07" />

</Grid>

</DataTemplate>

</BindableLayout.ItemTemplate>

</StackLayout>

</StackLayout>

</yummy:PancakeView>

<!-- END OF PHONES LAYOUT -->

</Grid>

68 of 69 11/28/2020, 4:03 PM
CabañasRD, from native to cross-platform with Xamarin Forms [1/4]. about:reader?url=http://talkwithangel.com/cabanasrd-from-native-to-cr...

</ScrollView>

</ContentPage.Content>

</ContentPage>

One more thing, let’s avoid the Dark mode for now, go to the iOS
project and add the following value to the info.plist:

<key>UIUserInterfaceStyle</key>

<string>Light</string>

Yessir, we are done!

This is the final result:

See you in the next post of this serie!

See the code on Github

69 of 69 11/28/2020, 4:03 PM

You might also like