You are on page 1of 11

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

talkwithangel.com

CabañasRD, from native to cross-


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

17-21 minutes

It’s Friday! that means we will continue with the third part of this
series of blogs about migrating and redesigning an existing and
popular app: CabañasRD.

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

1. From Adobe XD to XAML: Design and structure.

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 (this post).

4. Implementing continuous integration and continuous delivery to the


stores with App Center.

Moving the data to the cloud: Let’s make our


backend with Azure Functions and CosmosDB and
consume it from the app.

I decided to make another post for the backend, if your are focused
just in the app development you can continue reading this post
without worry about the API, we will be using the one that is already
deployed.

Our Xamarin app will consume this Azure Function:

GET https://cabanasrd-api.azurewebsites.net
/api/cabanas?code=YourKey

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

Add a new folder called “Configs” in the App/CabanasRD project, in


this new folder add a file called AppSettingsConstants.cs and put
this content:

public static class AppSettingsConstants

public const string ApiUrl = "https://cabanasrd-


api.azurewebsites.net";

public const string ApiKey = "YourAzureFunctionsKey";

You can see how to get the ApiKey in this post about “Creating a
simple serverless microservice using Azure Functions and
CosmosDB”.

For consuming the service we will use Refit, an automatic type-safe


REST library for .NET Core, Xamarin and .NET, so, let’s install the
NuGet package for the library in the shared project
(App/CabanasRD), once installed, the first thing we need is an
interface for defining the RestService methods, for this add a folder
called APIs in App/CabanasRD/Framework, in the new folder add
an interface named ICabanasAPI with the following definition:

public interface ICabanasAPI

[Get("/api/cabanas?code={key}")]

Task<List<MotelResponse>> GetMotels(string key);

As you can see, we dont have defined the MotelResponse class


yet, let’s add this new class to a folder called “Models” inside
App/CabanasRD/Framework/APIs, and fill it with:

public partial class MotelResponse

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

[JsonProperty("id")]

public string Id { get; set; }

[JsonProperty("name")]

public string Name { get; set; }

[JsonProperty("longitude")]

public string Longitude { get; set; }

[JsonProperty("latitude")]

public string Latitude { get; set; }

[JsonProperty("motelServices")]

public List<MotelResponseService> MotelServices { get; set; }

[JsonProperty("phones")]

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

[JsonProperty("takeCredictCards")]

public bool TakeCredictCards { get; set; }

[JsonProperty("ranking")]

public int Ranking { get; set; }

[JsonProperty("images")]

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

[JsonProperty("address")]

public string Address { get; set; }

[JsonProperty("state")]

public State State { get; set; }

[JsonProperty("descripcion")]

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

public object Descripcion { get; set; }

[JsonProperty("creditCards")]

public List<object> CreditCards { get; set; }

[JsonProperty("type")]

public int Type { get; set; }

[JsonProperty("isManagedByTheOwner")]

public bool IsManagedByTheOwner { get; set; }

[JsonProperty("rankingValue")]

public double RankingValue { get; set; }

public partial class MotelResponseService

[JsonProperty("service")]

public string Service { get; set; }

[JsonProperty("price")]

public double Price { get; set; }

[JsonProperty("type")]

public int Type { get; set; }

[JsonProperty("currencyType")]

public int CurrencyType { get; set; }

[JsonProperty("descriptionDetail")]

public object DescriptionDetail { get; set; }

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

public partial class State

[JsonProperty("id")]

public string Id { get; set; }

[JsonProperty("name")]

public string Name { get; set; }

This model was generated using the JSON response from the
service, with the help of https://quicktype.io/ we got the classes with
the fields mapped.

Before using the ICabanasAPI, we will need to register it as a


RestService in the App.xaml.cs file located in App/CabanasRD, and
add the following code in the RegisterTypes method:

....

protected override void RegisterTypes(IContainerRegistry


containerRegistry)

...

//Refit APIs

containerRegistry.RegisterInstance(RestService.For<ICabanasAPI>
(Configs.AppSettingsConstants.ApiUrl));

...

We are ready to consume the service by using the ICabanasAPI!

But… We don’t want to manually convert the service response


to our domain model, that’s why we are going to introduce
something called AutoMapper, a simple little library built to solve a
deceptively complex problem – getting rid of code that mapped one

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

object to another.

Install the AutoMapper NuGet package in the shared project


App/CabanasRD, then add the following code to init and configure
it in the App/CabanasRD/App.xaml.cs:

....

protected override void RegisterTypes(IContainerRegistry containerRegistry)

...

//AutoMapper

containerRegistry.RegisterInstance(GetMapperConfiguration().CreateMapper

private MapperConfiguration GetMapperConfiguration()

const string imagesExtension = "jpg";

var config = new MapperConfiguration(cfg =>

cfg.AllowNullCollections = true;

//MotelServices

cfg.CreateMap<Framework.APIs.Models.MotelResponseService,
Domain.Motels.MotelService>()

.ForMember(dest => dest.Name, source => source.MapFrom(src =>


src.Service))

.ForMember(dest => dest.Description, source => source.MapFrom(src =>


src.DescriptionDetail))

.ForMember(dest => dest.Price, source => source.MapFrom(src => src.Price));

//Motel

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

cfg.CreateMap<Framework.APIs.Models.MotelResponse,
Domain.Motels.Motel>()

.ForMember(dest => dest.Name, source => source.MapFrom(src =>


src.Name))

.ForMember(dest => dest.Id, source => source.MapFrom(src =>


int.Parse(src.Id)))

.ForMember(dest => dest.Latitude, source => source.MapFrom(src =>


double.Parse(src.Latitude)))

.ForMember(dest => dest.Longitude, source => source.MapFrom(src =>


double.Parse(src.Longitude)))

.ForMember(dest => dest.Services, source => source.MapFrom(src =>


src.MotelServices))

.ForMember(dest => dest.Images, source => source.MapFrom(src =>


src.Images.Select(i => new Domain.Motels.MotelImage{

Url = $"{i}.{imagesExtension}"

})))

.ForMember(dest => dest.Phones, source => source.MapFrom(src =>


src.Phones.Select(p => new Domain.Motels.MotelPhone

Number = p

})));

});

config.AssertConfigurationIsValid();

return config;

Now we are able to convert easily from the service response to our
domain model.

As we are following Interface Segregation principle, changing the

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

way we obtain our data is as simple as create another class that


implements IMotelsSource, let’s create the MotelsSource.cs class
in App/CabanasRD/Framework/DataSources with the following
content:

public class MotelsSource : IMotelsSource

private readonly ICabanasAPI _cabanasAPI;

private readonly IMapper _mapper;

public MotelsSource(ICabanasAPI cabanasAPI, IMapper


mapper)

_cabanasAPI = cabanasAPI;

_mapper = mapper;

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

try

var cabanasResponse = await


_cabanasAPI.GetMotels(Configs.AppSettingsConstants.ApiKey);

return _mapper.Map<List<APIs.Models.MotelResponse>,
List<Domain.Motels.Motel>>(cabanasResponse);

catch (ApiException ex)

switch (ex.StatusCode)

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

case HttpStatusCode.InternalServerError:

throw new SystemException(ex.Content);

case HttpStatusCode.Unauthorized:

throw new UnauthorizedAccessException(ex.Content);

default:

break;

throw ex;

One last thing for switching the InMemoryMotelsSource for the


MotelSource, go to the App/CabanasRD/App.xaml.cs and change
this:

...

protected override void RegisterTypes(IContainerRegistry


containerRegistry)

...

//Repositories & Data sources

...

//Comment or remove this line

//containerRegistry.Register<IMotelsSource,
InMemoryMotelsSource>();

//And replace for this one

containerRegistry.Register<IMotelsSource, MotelsSource>();

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

...

That’s all, run the app, wait a few seconds in the map and voilà!

Hey, this introduces new security and UX problems:

1. If we push this code to our repository the ApiKey will be exposed.

2. We don’t have a “Loading” indicator anywhere.

3. If any error occurred during the request the app will crash, there is
a need for handling the exceptions.

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

4. There are some motels without phones or services or images, we


need some empty states.

Let’s make a deal , I will resolve the first issue and you are going
to help me by resolving the rest of problems on GitHub by creating
a PR with the changes.

For manage the ApiKey as a secret we will use this wonderful tool
made by Dan Siegel called Mobile.BuildTools.

Install the Mobile.BuildTools NuGet package in the shared project


App/CabanasRD, once installed we need to create a file in the root
of App/CabanasRD called secrets.json and add it to the
.gitignore, now that the file is ignored open it and put this:

"ApiKey": "YOUR API KEY"

If you build the app it should be a new file in App/CabanasRD


/Helpers called Secrets.cs, it contains static references to the
secrets we put in the secrets.json, this autogenerated file should be
ignored too, for ignore both files add this to the .gitignore file:

src/App/CabanasRD/Helpers/Secrets.cs

src/App/CabanasRD/secrets.json

PD: If the build does not generate the Secrets.cs file, add it and the
Helpers folder manually and build again.

Nice, now our secrets are out of the source control!

Remove the ApiKey property from App/CabanasRD/Configs


/AppSettingsConstants.cs and replace any reference of this
removed prop with Helpers.Secrets.ApiKey rebuild and run the app.

Take your stuffs and go home, we are done!

See you in the next post of this serie!

See the code on Github

11 of 11 11/28/2020, 4:06 PM

You might also like