Flutter, API natives et plugins (2/3)
“Faut qu’on parle…”
- Dans la première partie nous avons vu les bases de l’utilisation des MethodChannels.
Nous allons maintenant regarder plus en détail une implémentation de base de la reconnaissance vocale.
Speech recognition
Dans Sytody, l’UI fontionne de la manière suivante :
- une fois la reconnaisance vocale détectée, un bouton permet de lancer l’enregistrement
- quand l’enregistrement démarre,
- le bouton d’écoute se transforme en bouton ‘Stop’, permettant de finaliser l’écoute et la transcription.
- le champ de trancription apparait, si il y a des stranscription intermédiaires, elles sont affichées, et un bouton [x]permet d’annuler l’écoute et donc la transcription en cours.
Fonctionnement de Sytody
API natives
Android(4.1+) et iOS(10+) proposent chacun une API de reconnaissance vocale :
- iOS : Speech API
- Android : SpeechRecognizer
Pour les utiliser depuis l’application Flutter, nous allons donc définir un canal dédié aux fonctionnalités de reconnaissance : activer , démarrer et arrêter l’analyse, afficher la transcription.
Le schéma nous montre les différentes étapes nécessaires :
-
Notre application Flutter demande l’activation ou la permission d’utiliser la reconnaissances vocale. Si c’est le 1er lancement, sur iOS et Android 7.1+, l’utilisateur doit accepter la demande.
-
une fois la demande acceptée, l’hôte, appelle invoque une méthode côté Flutter pour confirmer l’activation de la reconnaissance pour l’application demandeuse.
-
A partir de là, Flutter peut lancer l’analyse en invoquant la méthode “listen” via le canal dédié.
-
Une fois l’écoute lancée, l’hôte invoque une méthode
onRecognitionStarted()
-
Dès lors, sur iOS, l’application recevra
- les transcriptions intermédiaires (sujettes à corrections/améliorations),
- puis, une fois que l’utilisateur arrête la reconnaissance (
speech.stop()
), la transcription finalisée ( sur mon device Android 6.0, l’application reçoit seulement la transcription finale ).
1ère implémentation
Création du projet
flutter create -i swift --org mon.domaine projet_speech
-i swift
: on souhaite utiliser Swift pour le code iOS, et pas ObjC défini par défaut-a kotlin
: si on souhaite utiliser Kotlin à la place du Java par défaut côté Android--org mon.domaine
: namespace du projetprojet_speech
: le nom du projet
Flutter / Dart
On peut créer une classe SpeechRecognizer pour gérer les échanges Flutter/OS
const MethodChannel _speech_channel =
const MethodChannel("bz.rxla.flutter/recorder");
class SpeechRecognizer {
// gestion des appels effctués par l'hôte
// dans cette 1ère implémentation on définit un handler globale
// dans le plugin final dans la 3ème partie
static void setMethodCallHandler(handler) {
_speech_channel.setMethodCallHandler(handler);
}
// activation / permission
static Future activate() =>_speech_channel.invokeMethod("activate");
// démarrage de l'écoute
static Future start(String lang) =>_speech_channel.invokeMethod("start", lang);
// arrêt de l'écoute et annulation de la transcription
static Future cancel() =>_speech_channel.invokeMethod("cancel");
// arrêt de l'écoute et finalisation de la transcription
static Future stop() =>_speech_channel.invokeMethod("stop");
}
Dans cette première implémentation, une méthode handler globale est défini pour les appels venant de l’OS
Future _platformCallHandler(MethodCall call) async {
switch (call.method) {
case "onSpeechAvailability":
setState(() => isListening = call.arguments);
break;
case "onSpeech":
if (todos.isNotEmpty)
if (transcription == todos.last.label)
return;
setState(() => transcription = call.arguments);
break;
case "onRecognitionStarted":
setState(() => isListening = true);
break;
case "onRecognitionComplete":
setState(() {
// on ios user can have correct partial recognition
// => if user add it before complete recognition just clear the transcription
if (call.arguments == todos.last?.label)
transcription = '';
else
transcription = call.arguments;
});
break;
default:
print('Unknowm method ${call.method} ');
}
}
iOS / Swift
Côté iOS, c’est dans l’appDelegate qu’on créé ici le canal recorderChannel:FlutterMethodChannel
, et qu’on définit les handlers pour les différentes méthodes appelées.
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
recorderChannel = FlutterMethodChannel.init(name: "bz.rxla.flutter/recorder",
binaryMessenger: controller)
recorderChannel!.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if ("start" == call.method) {
self.startRecognition(lang: call.arguments as! String, result: result)
} else if ("stop" == call.method) {
self.stopRecognition(result: result)
} else if ("cancel" == call.method) {
self.cancelRecognition(result: result)
} else if ("activate" == call.method) {
self.activateRecognition(result: result)
} else {
result(FlutterMethodNotImplemented)
}
})
return true
}
l’implémentation de ces méthodes ne concerne pas Flutter, donc je ne rentre pas plus dans le détails. cf. AppDelegate.swift
Android / Java
même API côté Android, même si l’implémentation suit ici les principes d’Android.
speechChannel = new MethodChannel(getFlutterView(), SPEECH_CHANNEL);
speechChannel.setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
if (call.method.equals("activate")) {
result.success(true);
} else if (call.method.equals("start")) {
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, getLocale(call.arguments.toString()));
cancelled = false;
speech.startListening(recognizerIntent);
result.success(true);
} else if (call.method.equals("cancel")) {
speech.stopListening();
cancelled = true;
result.success(true);
} else if (call.method.equals("stop")) {
speech.stopListening();
cancelled = false;
result.success(true);
} else {
result.notImplemented();
}
}
}
);
Voilà pour une v0.1, et pour cette deuxième partie.
Dans la troisième et dernière partie, nous verrons comment modulariser ces fonctionnalités crossplatform, en créant un plugin dédié, facilement réutilisable.
> Flutter, API natives et plugins (3/3)