Mobiele apps programmeren met Xamarin – Toegang krijgen tot specifieke functionaliteiten via DependencyService

print

Inhoud


Wat vooraf ging


Inleiding

Sommige functionaliteiten zijn zodanig platform/device specifiek dat Xamarin geen algehele oplossing kan aanbieden. Het toevoegen van deze functionaliteiten dient echt specifiek geprogrammeerd te worden voor iOS, Android en UWP. Denk aan:

  • Tekst uitspreken.
  • Bestanden opslaan en openen.
  • Batterij-informatie opvragen.

Ook al dienen dze functionaliteiten specifiek per platform/device te gebeuren, toch dient Xamarin een mogelijk te bieden om deze specifiek functionaliteiten vanuit het hogere, gedeelde, niveau aan te kunnen spreken. De oplossing hiertoe is DependencyService.


Hoe implementeert u een DependencyService

Het implementeren van een DependencyService gebeurt in 4 stappen.

  • Bepaal de Interface – De interface definieert hoe de functionaliteit toegankelijk zal zijn. De interface bevat enkel de signature van de functies (de naam, parameters en het type van de geretourneerde waarde). Deze interface kan dan nadien gebruikt worden door de verschillende platformen zodat de specifieke implementatie, via de interface, over op een eenduidige manier kan aangesproken worden.
  • Implementatie per platform – Waar de interface enkel de signature bevat van de te gebruiken functie, wordt de functie per platform specifiek geprogrammeerd in een klasse die de interface aanroept.
  • Registreer – De geïmplementeerde klasse dient geregistreerd te worden als een DependencyService zodat deze, via de interface, kan gebruikt worden.
  • Roep de DependencyService aan – In de gedeelde code kan nu de geregistreerde DependencyService aangeroepen worden.


Praktisch voorbeeld – Text-to-Speech

Onderstaand voorbeeld legt uit hoe u getypte tekst kunt laten uitspreken door uw toestel, Text-to-Speech.

  • Start een nieuw Xamarin-project en geef het een passende naam (bv. TekstNaarSpraak). Ik werk in onderstaande voorbeelden op basis van een voorbeeld project met de naam UsingDependencyService

Hieronder ziet u de schematische voorstelling.

Bepaal de Interface

  • Rechts-klik het Navigation(Portable) project in de Solution Explorer en selecteer Add – New Item….
  • Selecteer Code – Interface en geef het een naam (bv. iTextToSpeech). Hoewel het niet verplicht is worden de namen van interfaces vaak begonnen met een i.

  • Voeg onderstaande code toe, de signatuur van de functie die later specifiek zal geïmplementeerd worden.
namespace UsingDependencyService
{
	public interface ITextToSpeech
	{
		void Speak (string text);
	}
}

De functie krijgt de naam Speak en verwacht een tekst als input/parameter.

Zoals reeds aangehaald wordt in de interface enkel de signatuur geplaatst en wordt de uitwerking van de functie overgeheveld naar de specifieke omgeving (Android, iOS of UWP).

Implementatie per platform

Voeg aan ieder project een klasse toe.

  • Rechts-klik het gewenste project (iOS, Android, UWP) in de Solution Explorer en selecteer Add – New Item….
  • Selecteer Code – Class en geef het een naam (bv. TextToSpeech_iOS).

Voor iedere klasse passen we de klasse als volgt aan (ik neem hier iOS als voorbeeld). Onderstaande code werd automatisch gegenereerd voor de iOS-klasse.

namespace UsingDependencyService.iOS
{
    class TextToSpeech_iOS
    {

    }
}

Voeg nu het volgende toe:

  • Baseer de klasse op de interface.
  • Voeg een constructor, zonder parameters, toe.
  • Voeg de interface-methode toe.

De code wordt nu:

namespace UsingDependencyService.iOS
{
    class TextToSpeech_iOS : ITextToSpeech
    {
        public TextToSpeech_iOS()
        {

        }

        public void Speak(string text)
        {

        }
    }
}
  • Herhaal dit voor Android en UWP.

Vervolgens gaan we nu de platform-specifieke code toevoegen. Omdat deze platform specifiek is ga ik deze niet in detail bespreken. Dit is uitgeteste en goedgekeurde, officiële code die ik gewoon kopieer en plak.

iOS

using System;
using AVFoundation;
using Xamarin.Forms;

namespace UsingDependencyService.iOS
{
	public class TextToSpeech_iOS : ITextToSpeech
	{
		public TextToSpeech_iOS ()
		{
		}

		public void Speak (string text)
		{
			var speechSynthesizer = new AVSpeechSynthesizer ();

			var speechUtterance = new AVSpeechUtterance (text) {
				Rate = AVSpeechUtterance.MaximumSpeechRate/4,
				Voice = AVSpeechSynthesisVoice.FromLanguage ("en-US"),
				Volume = 0.5f,
				PitchMultiplier = 1.0f
			};

			speechSynthesizer.SpeakUtterance (speechUtterance);
		}
	}
}

Android

Zoals wel vaker bij Android is de code ietsje complexer.

using System;
using Android.Speech.Tts;
using Xamarin.Forms;
using System.Collections.Generic;

namespace UsingDependencyService.Android
{
	public class TextToSpeech_Android : Java.Lang.Object, ITextToSpeech, TextToSpeech.IOnInitListener
	{
		TextToSpeech speaker; string toSpeak;

		public TextToSpeech_Android ()
		{
		}

		public void Speak (string text)
		{
			var c = Forms.Context; 
			toSpeak = text;
			if (speaker == null) {
				speaker = new TextToSpeech (c, this);
			} else {
				var p = new Dictionary<string,string> ();
				speaker.Speak (toSpeak, QueueMode.Flush, p);
				System.Diagnostics.Debug.WriteLine ("spoke " + toSpeak);
			}
		}

		#region IOnInitListener implementation
		public void OnInit (OperationResult status)
		{
			if (status.Equals (OperationResult.Success)) {
				System.Diagnostics.Debug.WriteLine ("speaker init");
				var p = new Dictionary<string,string> ();
				speaker.Speak (toSpeak, QueueMode.Flush, p);
			} else {
				System.Diagnostics.Debug.WriteLine ("was quiet");
			}
		}
		#endregion
	}
}

UWP

using System;
using Windows.Media.SpeechSynthesis;
using Windows.UI.Xaml.Controls;
using Xamarin.Forms;

namespace UsingDependencyService.UWP
{
    public class TextToSpeech_UWP : ITextToSpeech
    {
        public TextToSpeech_UWP ()
        {
        }

        public async void Speak(string text)
        {
            MediaElement mediaElement = new MediaElement();

            var synth = new SpeechSynthesizer();
            var stream = await synth.SynthesizeTextToStreamAsync(text);

            mediaElement.SetSource(stream, stream.ContentType);
            mediaElement.Play();
        }
    }
}

Voor UWP moet ook nog de microfoon geactiveerd worden.

  • Open het Properties-scherm van het UWP-project.
  • Klik op Package Manifest.
  • Vink Microphone aan onder Capabilities.

Registratie

De registratie gebeurt via volgende code [assembly: Dependency (typeof (TextToSpeech_iOS))] waarbij verwezen wordt naar de respectievelijk klasse. Tevens dient u ook een verwijzing naar het eigen project op te nemen via using UsingDependencyService.iOS;.

De aangepaste code wordt nu:

iOS

using System;
using AVFoundation;
using Xamarin.Forms;
using UsingDependencyService.iOS;

[assembly: Dependency (typeof (TextToSpeech_iOS))]

namespace UsingDependencyService.iOS
{
	public class TextToSpeech_iOS : ITextToSpeech
	{
		public TextToSpeech_iOS ()
		{
		}

		public void Speak (string text)
		{
			var speechSynthesizer = new AVSpeechSynthesizer ();

			var speechUtterance = new AVSpeechUtterance (text) {
				Rate = AVSpeechUtterance.MaximumSpeechRate/4,
				Voice = AVSpeechSynthesisVoice.FromLanguage ("en-US"),
				Volume = 0.5f,
				PitchMultiplier = 1.0f
			};

			speechSynthesizer.SpeakUtterance (speechUtterance);
		}
	}
}

Android

using System;
using Android.Speech.Tts;
using Xamarin.Forms;
using System.Collections.Generic;
using UsingDependencyService.Android;

[assembly: Dependency (typeof (TextToSpeech_Android))]

namespace UsingDependencyService.Android
{
	public class TextToSpeech_Android : Java.Lang.Object, ITextToSpeech, TextToSpeech.IOnInitListener
	{
		TextToSpeech speaker; string toSpeak;

		public TextToSpeech_Android ()
		{
		}

		public void Speak (string text)
		{
			var c = Forms.Context; 
			toSpeak = text;
			if (speaker == null) {
				speaker = new TextToSpeech (c, this);
			} else {
				var p = new Dictionary<string,string> ();
				speaker.Speak (toSpeak, QueueMode.Flush, p);
				System.Diagnostics.Debug.WriteLine ("spoke " + toSpeak);
			}
		}

		#region IOnInitListener implementation
		public void OnInit (OperationResult status)
		{
			if (status.Equals (OperationResult.Success)) {
				System.Diagnostics.Debug.WriteLine ("speaker init");
				var p = new Dictionary<string,string> ();
				speaker.Speak (toSpeak, QueueMode.Flush, p);
			} else {
				System.Diagnostics.Debug.WriteLine ("was quiet");
			}
		}
		#endregion
	}
}

UWP

using System;
using Windows.Media.SpeechSynthesis;
using Windows.UI.Xaml.Controls;
using Xamarin.Forms;
using UsingDependencyService.UWP;

[assembly: Dependency(typeof(TextToSpeech_UWP))]

namespace UsingDependencyService.UWP
{
    public class TextToSpeech_UWP : ITextToSpeech
    {
        public TextToSpeech_UWP ()
        {
        }

        public async void Speak(string text)
        {
            MediaElement mediaElement = new MediaElement();

            var synth = new SpeechSynthesizer();
            var stream = await synth.SynthesizeTextToStreamAsync(text);

            mediaElement.SetSource(stream, stream.ContentType);
            mediaElement.Play();
        }
    }
}

Roep de DependencyService aan

Het aanroepen van de DependencyService gebeurt nu vanuit het algemene project door de methode binnen de interface aan te roepen:

DependencyService.Get<interface>().methode();

Meer concreet wordt dit dus:

DependencyService.Get<ITextToSpeech>().Speak("Hello, Geert Linthoudt !");

Onderstaande code toont hoe dit kan worden toegevoegd aan de Clicked-event.

XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="UsingDependencyService.TestTextToSpeech">
    <StackLayout>
        <Button x:Name="Test" Text="Test" Clicked="Test_Clicked"></Button>
    </StackLayout>
</ContentPage>

Code-behind

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

namespace UsingDependencyService
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TestTextToSpeech : ContentPage
    {
        public TestTextToSpeech()
        {
            InitializeComponent();
        }

        private void Test_Clicked(object sender, EventArgs e)
        {
            DependencyService.Get<ITextToSpeech>().Speak("Hello, Geert Linthoudt !");
        }
    }
}

Plug-in

Lijkt u dit toch allemaal een beetje teveel werk, dan kunt u ook de TextToSpeechPlugin installeren.

Het enige wat u dan nog moet doen is onderstaande code aanroepen:

await CrossTextToSpeech.Current.Speak("Text to speak");

Praktisch voorbeeld – Bestanden opslaan en openen

Voor het opslaan en openen van bestanden dienen specifieke mappen aangesproken, ieder platform komt immers met zijn eigen bestandsysteem. De programmeercode zal dus ook platform/device-specifiek moeten zijn.

  • Start een nieuw Xamarin-project en geef het een passende naam (bv. BestandenOpslaanOpenen). Ik werk in onderstaande voorbeelden op basis van een voorbeeld project met de naam UsingDependencyService

Bepaal de Interface

  • Rechts-klik het Navigation(Portable) project in de Solution Explorer en selecteer Add – New Item….
  • Selecteer Code – Interface en geef het een naam (bv. ISaveAndLoad). Hoewel het niet verplicht is worden de namen van interfaces vaak begonnen met een i.
  • Voeg onderstaande code toe, de signatuur van de functies die later specifiek zullen geïmplementeerd worden.
using System.Threading.Tasks;

namespace UsingDependencyService
{
    public interface ISaveAndLoad
    {
        Task SaveTextAsync(string filename, string text);
        Task<string> LoadTextAsync(string filename);
        bool FileExists(string filename);
    }
}

Merk op dat we hier meerdere methoden “klaar zetten”. 2 ervan zijn taken (en laten dus asynchroon programmeren toe) en hebben nood aan de bibliotheek using System.Threading.Tasks;.

Implementatie en registratie per platform

Omdat de implementatie en de registratie op dezelfde plaats gebeurt neem ik ze hier samen. Ik ga opnieuw de specifieke code niet in detail bespreken, omdat ze platform/device-specifiek is. Merk op dat hier gekozen is telkens, per platform, dezelfde naam SaveAndLoad te kiezen.

  • Rechts-klik het gewenste project (iOS, Android, UWP) in de Solution Explorer en selecteer Add – New Item….
  • Selecteer Code – Class en geef het een naam (bv. SaveAndLoad).

iOS

using Foundation;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
using UsingDependencyService.iOS;

[assembly: Dependency(typeof(SaveAndLoad))]
namespace UsingDependencyService.iOS
{
    class SaveAndLoad : ISaveAndLoad
    {
        public SaveAndLoad() { }

        public static string DocumentsPath
        {
            get
            {
                var documentsDirUrl = NSFileManager.DefaultManager.GetUrls(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomain.User).Last();
                return documentsDirUrl.Path;
            }
        }

        static string CreatePathToFile(string fileName)
        {
            return Path.Combine(DocumentsPath, fileName);
        }

        public bool FileExists(string filename)
        {
            return File.Exists(CreatePathToFile(filename));
        }

        public async Task<string> LoadTextAsync(string filename)
        {
            string path = CreatePathToFile(filename);
            using (StreamReader sr = File.OpenText(path))
                return await sr.ReadToEndAsync();
        }

        public async Task SaveTextAsync(string filename, string text)
        {
            string path = CreatePathToFile(filename);
            using (StreamWriter sw = File.CreateText(path))
                await sw.WriteAsync(text);
        }
    }
}

Android

using System.Threading.Tasks;
using System.IO;
using Xamarin.Forms;
using UsingDependencyService.Droid;

[assembly: Dependency(typeof(SaveAndLoad))]
namespace UsingDependencyService.Droid
{
    class SaveAndLoad : ISaveAndLoad
    {
        public SaveAndLoad() { }

        string CreatePathToFile(string filename)
        {
            var docsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
            return Path.Combine(docsPath, filename);
        }

        public bool FileExists(string filename)
        {
            return File.Exists(CreatePathToFile(filename));
        }

        public async Task<string> LoadTextAsync(string filename)
        {
            var path = CreatePathToFile(filename);
            using (StreamReader sr = File.OpenText(path))
                return await sr.ReadToEndAsync();
        }

        public async Task SaveTextAsync(string filename, string text)
        {
            var path = CreatePathToFile(filename);
            using (StreamWriter sw = File.CreateText(path))
                await sw.WriteAsync(text);
        }
    }
}

UWP

using System;
using System.Threading.Tasks;
using Windows.Storage;
using Xamarin.Forms;
using UsingDependencyService.UWP;

[assembly: Dependency(typeof(SaveAndLoad))]
namespace UsingDependencyService.UWP
{
    class SaveAndLoad : ISaveAndLoad
    {
        public SaveAndLoad() { }

        public bool FileExists(string filename)
        {
            var localFolder = ApplicationData.Current.LocalFolder;
            try
            {
                localFolder.GetFileAsync(filename).AsTask().Wait();
                return true;
            }
            catch
            {
                return false;
            }
        }

        public async Task<string> LoadTextAsync(string filename)
        {
            StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
            StorageFile sampleFile = await storageFolder.GetFileAsync(filename);
            string text = await Windows.Storage.FileIO.ReadTextAsync(sampleFile);
            return text;
        }

        public async Task SaveTextAsync(string filename, string text)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;
            StorageFile sampleFile = await localFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
            await FileIO.WriteTextAsync(sampleFile, text);
        }
    }
}

Roep de DependencyService aan

Het aanroepen van de DependencyService gebeurt nu vanuit het algemene project door de methode binnen de interface aan te roepen:

DependencyService.Get<interface>().methode();

Omdat er hier meerdere methoden in de interface beschikbaar zijn zou u kunnen opteren om de interface aan een variabele toe te kennen.

var fileService = DependencyService.Get<ISaveAndLoad>();

Nadien kunnen de methoden als volgt aangeroepen worden, gebruikmakend van de variabele. Onderstaand voorbeeld is geen volledig werkend programma, maar voorbeeldcode.

const string fileName = "temp.txt";
Button loadButton, saveButton;
var input = new Entry { Text = "" };
var output = new Label { Text = "" };

var fileService = DependencyService.Get<ISaveAndLoad>();

await fileService.SaveTextAsync(fileName, input.Text);
output.Text = await fileService.LoadTextAsync(fileName);
loadButton.IsEnabled = fileService.FileExists(fileName);

Andere praktische voorbeelden

Onderstaande praktische voorbeelden vindt u in de officiële documentatie:


Hoor het eens van iemand anders


Behandelde Basiscompetenties uit de module ICT Programmeren – Integratie externe functionaliteiten

  • IC BC024 – * kan zijn eigen deskundigheid inzake ICT opbouwen
  • IC BC232 – kan digitale tools gebruiken om modellen, simulaties en visualisaties van de realiteit te maken
  • IC BC254 – kan externe content integreren en structureren

Geef een reactie

  • Abonneer je op deze website d.m.v. e-mail

    Voer je e-mailadres in om je in te schrijven op deze website en e-mailmeldingen te ontvangen van nieuwe berichten.