Xamarin par l’exemple : “Metsys Music Search” 1/3

Il y a plus de 2 ans, Microsoft annonçait le rachat de Xamarin. Aujourd’hui, le résultat de cette alliance aboutit à une large gamme d’outils rendant le développement “Cross-Platform” plus rapide et plus facile pour les développeurs.

1. Introduction : qu’est-ce que Xamarin ?

Xamarin est une plateforme de développement d’application mobile permettant la création d’applications natives et multiplateformes (iOS, Android et Windows) à partir d’une base de code commune C#/.NET. Celle-ci permet de réutiliser de 75 à presque 100 % du code entre les plateformes. Les applications écrites avec Xamarin et C# ont un accès complet aux API de la plateforme sous-jacente, et permettent de créer des interfaces utilisateurs natives et de compiler en mode natif. L’impact sur les performances de l’exécution est donc faible. Cette réutilisabilité est encore plus vraie avec Xamarin Forms qui permet de mutualiser les interfaces utilisateurs à partir d’une base commune XAML.

2. RoadMap

Quoi de mieux qu’une démonstration pour présenter rapidement les possibilités de Xamarin? Dans cet article, nous allons créer un projet permettant de chercher des artistes de musique. Nous pourrons ainsi lister et écouter des extraits de leurs albums. Cette application sera utilisable sur iOS, Android et Windows :

3. Création du projet

Avec Visual Studio, créez un nouveau projet de type “Cross-Platform” :

Vérifiez que toutes les plateformes (Android, iOS et Windows(UWP)) sont sélectionnées et choisissez “.NET Standard” :

3.1 Architecture du projet

La solution se compose de 4 projets : le “noyau” du projet (c’est lui qui va mutualiser le code et les interfaces utilisateurs), la partie Android, la partie iOS et celle pour Windows 10 (UWP) :

Dans le projet “noyau” créez une structure de répertoire comme ci-dessous afin d’organiser nos futures sources (et supprimez le fichier MainPage.xaml) :

3.2 Création des classes “Business”, “Converters” et “Extensions”

Créez un nouveau fichier “Deezer.cs”. Ce fichier contiendra toutes les classes nécessaires à l’alimentation des vues :

    public class ArtistViewModel
    {
        public List<ArtistInfo> ListeArtists { get; set; } = new List<ArtistInfo>();
    }

    public class ArtistInfo
    {
        public string id { get; set; }
        public string name { get; set; }
        public string link { get; set; }
        public string picture { get; set; }
        public string picture_small { get; set; }
        public string picture_medium { get; set; }
        public string picture_big { get; set; }
        public string picture_xl { get; set; }
        public int nb_album { get; set; }
        public int nb_fan { get; set; }
        public bool radio { get; set; }
        public string tracklist { get; set; }
        public string type { get; set; }
    }

    public class TrackViewModel
    {
        public List<AlbumInfo> ListeAlbums { get; set; } = new List<AlbumInfo>();

        public string artistName { get; set; }

        public string artistPicture { get; set; }
    }

    public class AlbumInfo
    {
        public string id { get; set; }
        public string title { get; set; }
        public string link { get; set; }
        public string cover { get; set; }
        public string cover_small { get; set; }
        public string cover_medium { get; set; }
        public string cover_big { get; set; }
        public string cover_xl { get; set; }
        public int genre_id { get; set; }
        public int fans { get; set; }
        public string release_date { get; set; }
        public string record_type { get; set; }
        public string tracklist { get; set; }
        public bool explicit_lyrics { get; set; }
        public string type { get; set; }
        public string Typetype { get; set; }
    }

    public class TrackInfo
    {
        public int id { get; set; }
        public bool readable { get; set; }
        public string title { get; set; }
        public string title_short { get; set; }
        public string title_version { get; set; }
        public string isrc { get; set; }
        public string link { get; set; }
        public int duration { get; set; }
        public int track_position { get; set; }
        public int disk_number { get; set; }
        public int rank { get; set; }
        public bool explicit_lyrics { get; set; }
        public string preview { get; set; }
        public string type { get; set; }
    }

Créez une classe “DataService.cs”. Cette classe est une sorte de proxy qui permettra de déserialiser les contenus venant des appels web :

using Newtonsoft.Json;
using System.Net.Http;
using System.Threading.Tasks;

namespace Metsys.Music.Search
{
    class DataService
    {

        public static async Task<dynamic> getDataFromService(string queryString)
        {
            HttpClient client = new HttpClient();
            var response = await client.GetAsync(queryString);

            dynamic data = null;
            if (response != null)
            {
                string json = response.Content.ReadAsStringAsync().Result;

                data = JsonConvert.DeserializeObject(json);
            }

            return data;
        }

        public static async Task<dynamic> getStreamFromService(string queryString)
        {
            HttpClient client = new HttpClient();
            var response = await client.GetAsync(queryString);

            dynamic data = null;
            if (response != null)
            {
                System.IO.Stream stream = response.Content.ReadAsStreamAsync().Result;

                data = stream;
            }

            return data;
        }

    }
}

Pour que cette classe fonctionne, ajoutez les composants “nuget” suivants :

Enfin, ajoutez la classe “Core.cs” qui permettra d’utiliser l’Api de “Deezer” pour nos requêtes de recherche :

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Metsys.Music.Search
{
    class Core
    {
        public static async Task<List<ArtistInfo>> GetArtists(string artist)
        {
            string queryString = "https://api.deezer.com/search/artist?q="
                + artist;

            dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);

            if (results["data"] != null)
            {
                List<ArtistInfo> artists = new List<ArtistInfo>();
                artists = results["data"].ToObject<List<ArtistInfo>>();
                return artists;
            }
            else
            {
                return null;
            }
        }

        public static async Task<List<AlbumInfo>> GetAlbums(string id)
        {
            string queryString = $"https://api.deezer.com/artist/{id}/albums";

            dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);

            if (results["data"] != null)
            {
                List<AlbumInfo> albums = new List<AlbumInfo>();
                albums = results["data"].ToObject<List<AlbumInfo>>();

                return albums;
            }
            else
            {
                return null;
            }
        }

        public static async Task<string> GetSample(string id)
        {
            string queryString = $"https://api.deezer.com/album/{id}/tracks";

            dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);

            if (results["data"] != null)
            {
                List<TrackInfo> tracks = new List<TrackInfo>();
                tracks = results["data"].ToObject<List<TrackInfo>>();
                return tracks.FirstOrDefault().preview;
            }
            else
            {
                return string.Empty;
            }
        }
    }
}

Pour que cette classe fonctionne, ajoutez les composants “nuget” suivants :

Rajoutez les deux classes ci-dessous dans le répertoire “Converter”. Ces deux classes vont permettre de gérer les affichages entre les vues et les modèles :

using System;
using System.Globalization;
using Xamarin.Forms;

namespace Metsys.Music.Search.Converter
{
    public class BooleanToOpacityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if ((bool)value)
                return 0.3;
            else
                return 1;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}
using System;
using System.Globalization;
using Xamarin.Forms;

namespace Metsys.Music.Search.Converter
{
    public class InvertBooleanConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return !(bool)value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}

Enfin, rajoutez le fichier “ImageResourceExtension.cs” dans le répertoire Extensions. Il nous permettra d’éviter de rajouter les fichiers ressources (images par exemple) à tous nos projets (iOS, Android et Windows10(UWP)) mais de n’utiliser que celle du noyau (très pratique) :

using System;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;


namespace Metsys.Music.Search.Extensions
{
    [ContentProperty(nameof(Source))]
    public class ImageResourceExtension : IMarkupExtension
    {
        public string Source { get; set; }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (Source == null)
            {
                return null;
            }

            var imageSource = ImageSource.FromResource(Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);

            return imageSource;
        }
    }
}

3.3 Création de la vue “HomeView”

Dans le répertoire Views, ajoutez un nouvel élément de type Xamarin.Forms ->Page de contenu (attention à ne pas sélectionner Page de contenu(C#)):

Remplacez le code XAML existant par celui ci-dessous :

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:extension="clr-namespace:Metsys.Music.Search.Extensions"
             xmlns:converter="clr-namespace:Metsys.Music.Search.Converter"
             x:Class="Metsys.Music.Search.Views.HomeView">

    <ContentPage.Resources>
        <ResourceDictionary>
            <converter:InvertBooleanConverter x:Key="InvertBooleanConverter" />
            <converter:BooleanToOpacityConverter x:Key="BooleanToOpacityConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid>

        <Grid IsEnabled="{Binding Source={x:Reference indicator}, Path=IsRunning, Converter={StaticResource InvertBooleanConverter}}"
              Opacity="{Binding Source={x:Reference indicator}, Path=IsRunning, Converter={StaticResource BooleanToOpacityConverter}}">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="2*"/>
                <RowDefinition/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>

            <!--Background Image-->
            <Image Source="{extension:ImageResource Source=Metsys.Music.Search.Resources.fondbanner.png}" Aspect="AspectFill"
               Grid.RowSpan="2"/>
            <Image Source="{extension:ImageResource Source=Metsys.Music.Search.Resources.logoMetsys.png}" 
               Grid.RowSpan="2" Grid.Row="1" HorizontalOptions="Center" VerticalOptions="Center" Margin="25"/>

            <!--TITLE APP-->
            <Label Text="Metsys Music Search" TextColor="#FF783CBD" 
               HorizontalOptions="Center" 
               Grid.Row="3"    
               VerticalOptions="Start" 
               FontSize="Large"/>

            <!--SEARCH area-->
            <Entry x:Name="authorEntry" Grid.Row="4" TextColor="#FF783CBD" Margin="30,0"
                        PlaceholderColor="#FFC7ADE5"
                        IsSpellCheckEnabled="True"
                        ReturnType="Search"
                        Placeholder="Search your artist..." 
                        VerticalOptions="Center"
                        HeightRequest="70" 
                        HorizontalOptions="FillAndExpand"                        
                        />

            <!--TITLE and Powered by DEEZER-->
            <Grid Grid.Row="5">
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>

                <Image Source="{extension:ImageResource Source=Metsys.Music.Search.Resources.deezer.png}" Margin="30,0,10,0"
               Grid.RowSpan="2" HorizontalOptions="Start" VerticalOptions="Center" HeightRequest="45" WidthRequest="45" />

                <Label Text="Powered by Deezer (developper api deezer)" TextColor="#FFBC9C16" HorizontalOptions="StartAndExpand"                   
                   VerticalOptions="End"
                   Grid.Column="1" FontSize="Micro"/>
                <Label HorizontalOptions="StartAndExpand" 
                   VerticalOptions="Start"
                   Grid.Row="1" Grid.Column="1" FontSize="Micro">
                    <Label.FormattedText>
                        <FormattedString>
                            <Span Text="https://developers.deezer.com/api" TextColor="#FFBC9C16" FontSize="Micro">
                                <Span.GestureRecognizers>
                                    <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
                                </Span.GestureRecognizers>
                            </Span>
                        </FormattedString>
                    </Label.FormattedText>
                </Label>
            </Grid>

            <!--Copyright-->
            <Label HorizontalOptions="Center" VerticalOptions="End" Margin="0,0,0,10" Grid.Row="6">
                <Label.FormattedText>
                    <FormattedString>
                        <Span Text="Copyright Metsys 2018 : " TextColor="#FFBC9C16" FontSize="Micro" />
                        <Span Text="http://www.metsys.fr" TextColor="#FFBC9C16" FontSize="Micro">
                            <Span.GestureRecognizers>
                                <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped_1"/>
                            </Span.GestureRecognizers>
                        </Span>
                    </FormattedString>
                </Label.FormattedText>
            </Label>

        </Grid>

        <ActivityIndicator x:Name="indicator" IsRunning="False" Color="#FF783CBD" 
                           IsVisible="False" VerticalOptions="Center" HorizontalOptions="Center">
            <ActivityIndicator.HeightRequest>
                <OnPlatform x:TypeArguments="x:Double">
                    <On Platform="UWP" Value="250" />
                    <On Platform="Android" Value="70" />
                    <On Platform="iOS" Value="200" />
                </OnPlatform>
            </ActivityIndicator.HeightRequest>
            <ActivityIndicator.WidthRequest>
                <OnPlatform x:TypeArguments="x:Double">
                    <On Platform="UWP" Value="250" />
                    <On Platform="Android" Value="70" />
                    <On Platform="iOS" Value="200" />
                </OnPlatform>
            </ActivityIndicator.WidthRequest>
        </ActivityIndicator>

    </Grid>


</ContentPage>

N’oubliez pas de rajouter le namespace “extensions” et “converter”. Il est possible que vous ne soyez pas avec la dernière version “nuget” des ‘Xamarins.Forms” (cela dépend de votre version de Visual Studio), aussi, vous pouvez les mettre à jour comme suit pour l’ensemble des 4 projets :

Rajoutez le code-behind dans la classe “HomeView.xaml.cs” :

        private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            Device.OpenUri(new System.Uri(@"https://developers.deezer.com/api"));
        }

        private void TapGestureRecognizer_Tapped_1(object sender, EventArgs e)
        {
            Device.OpenUri(new System.Uri(@"http://www.metsys.fr"));
        }

Et enfin, rajoutez dans le répertoire “Resources” les éléments ci-dessous sans oublier d’assigner leur propriété “Action de génération” à “Ressource incorporée” :

Fichiers ressources : fondbanner, deezer et logoMetsys

4. Exécution du projet :

Il ne reste plus qu’à faire pointer l’application sur cette nouvelle vue créée en changeant le fichier “App.xaml.cs” comme suit :

L’application est prête à être testée sur toutes les plateformes (Windows10 (UWP), Android et iOS) :

    

 

A propos de l'auteur

Je suis Architecte Technique et passionné par le développement mobile et la réalité augmentée. Je baigne dans l’univers Microsoft depuis maintenant une vingtaine d’année.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.