Skip to main content

'But without my voice, how can I...' - Using DependencyService to implement Text-to-Speech

·5 mins

If Ariel would’ve been born a few decades later (or existed at all, for that matter..), there would be no problem! She could just develop an app with text-to-speech capabilities and let that do the talking for her. In this post I will be looking at the DependencyService (documentation) and to demonstrate how that works I will implement a text-to-speech (TTS) function to my already infamous 4DotNet app, which we have seen in the last few posts.

DependencyService #

To start off, what is a DepencencyService? According to Xamarin the definition is;

Xamarin.Forms includes a DependencyService to let shared code to easily resolve Interfaces to platform-specific implementations, allowing you to access features of the iOS, Android and Windows Phone SDKs from your PCL or Shared Project.

In real-life it means; when you need to access native functionality, wrap it in an interface, register it to Xamarin and the DependencyService will load the appropriate implementation when needed.

I will be describing it while implementing TTS. All platforms support TTS, but all of them have some distinctive methods, and thus we will need a specific implementation which is facilitated by the DependencyService. Life doesn’t have to be hard!

Under the sea, under the sea.. #

The DependencyService consists of three parts:

  • Interface - An interface which describes how the functionality will look like on the outside, what methods can be called by the DependencyService. You would want to define this interface in your shared code.
  • Registration - You need to let Xamarin (or better, the DependencyService) know that there is a implementation (for that specific platform) of the above interface, so you need to register it. This will be in your respective app.
  • Location - By calling DependencyService.Get<> you will obtain the platform specific implementation (the above registration) of your interface on which you can call the methods defined in your interface.

Actually that’s all there is to it! So let’s sea (ha-ha) how it looks in code.

One more thing; you will need to provide the implementation of your interface with a parameterless constructor in order to be resolved by the DependencyService.

The code #

The interface #

First I will open up my shared FourDotNet project and define a new interface called ITextToSpeech like this:

public interface ITextToSpeech { void Speak (string text); }

And that’s it! Of course there is nothing stopping you from adding more methods if needed.

The registration (implementation) #

Now we need three registrations, which are actually implementations of the ITextToSpeech. First, the iOS one.

[assembly: Xamarin.Forms.Dependency (typeof (TextToSpeech_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);
}

}

You immediately notice two things; the annotation on top of the class. This is our actual registration. This tells the DependencyService that it can find the iOS implementation (TextToSpeech_iOS) of the ITextToSpeech interface, right here.

Second; the parameterless constructor I have warned you about before.

Only thing left is the Speak method. This contains code to let Siri talk. You see some iOS specific types like  the AVSpeechSynthesizer.

Let’s look at the other two. Android first.

[assembly: Xamarin.Forms.Dependency (typeof (TextToSpeech_Android))] public class TextToSpeech_Android : Java.Lang.Object, ITextToSpeech, TextToSpeech.IOnInitListener { TextToSpeech speaker; string toSpeak;

public TextToSpeech\_Android () {}

public void Speak (string text)
{
    var ctx = Forms.Context; // useful for many Android SDK features
    toSpeak = text;
    if (speaker == null) {
        speaker = new TextToSpeech (ctx, this);
    } else {
        var p = new Dictionary<string,string> ();
        speaker.Speak (toSpeak, QueueMode.Flush, p);
    }
}

#region IOnInitListener implementation
public void OnInit (OperationResult status)
{
    if (status.Equals (OperationResult.Success)) {
        var p = new Dictionary<string,string> ();
        speaker.Speak (toSpeak, QueueMode.Flush, p);
    } 
}
#endregion

}

And last but not least, Windows Phone.

[assembly: Xamarin.Forms.Dependency (typeof (TextToSpeech_WinPhone))] public class TextToSpeech_WinPhone : ITextToSpeech { public TextToSpeech_WinPhone() {}

public async void Speak(string text)
{
    SpeechSynthesizer synth = new SpeechSynthesizer();
    await synth.SpeakTextAsync(text);
}

}

On Windows Phone you will also need to request permission by the App Manifest. The concerning permission is ID_CAP_SPEECH_RECOGNITION.

Needless to say, these three classes will go into their respective projects, in my case: FourDotNet.iOS, FourDotNet.Droid and FourDotNet.WinPhone.

The location #

Now for the fun part; bringing it all together.

Back to our shared code. I would like my app to tell me something about a certain employee, so in my EmployeePage.xaml I will add a simple button in the toolbar to call my code.

… <ContentPage.ToolbarItems> …

TTS Screenshot

On the tap event this will happen:

private void SpeechToolbarItem_OnClicked(object sender, EventArgs e) { DependencyService.Get().Speak(String.Format("{0} is one of our best employees! "

  • “You can ask him anything about {1}”, _employee.Name, String.Join(", “, _employee.SkillTags.Select(s => s.Description).Take(3)))); }

 

Here you see how the DependencyService is called, and we request the platform-specific implementation of our ITextToSpeech interface. The DependencyService will locate the right implementation for us, which it knows because we have registered it before. Because we defined the Speak method in our interface we can call it with the right parameter.

In this case it will tell something modest about the chosen employee and will sum up some of his skills.

Unfortunately GIFs don’t do sound yet, so you will just have to believe me that this will work. Or, even better, try it yourself!

Fin. #

Although it is a very cool technique and we can’t stop talking about our employees, it is a rather useless feature which probably won’t end up in the final version.

But it was very fun to play with and discover how easy it is to implement platform-specific features!

To be complete; when you want to call the same code for all platforms you will have to implement (and register) the interface for all. Failing to do so will result in a NullReferenceException at runtime.

Want to see the DependencyService in action? See my vlog about it on my YouTube channel.