Cet article explique la notion de Semantics (= Sémantique) en Flutter.

Difficulté: Débutant

Avant-propos

Si vous lisez un code lié à Flutter, vous remarquerez parfois l’utilisation de Semantics ou SemanticsConfiguration mais la documentation officielle en dit très peu sur ce sujet intéressant.

Cet article est une introduction au sujet et montre combien il est important et intéressant pour votre application de prendre cela en considération.

En bref - De quoi s’agit-il?

La documentation officielle dit ce qui suit à propos de la classe Semantics:

Un widget qui annote l’arbre des widgets avec une description de la signification des widgets Utilisé par les outils d’accessibilité, les moteurs de recherche et d’autres logiciels d’analyse sémantique pour déterminer la signification de l’application.

Personnellement, je ne trouve pas cela très explicite. Donc, je vais utiliser mes propres mots:

En bref, la notion de Semantics est:

  • totalement optionnelle (ce qui signifie que vous pourriez vivre sans vous en soucier mais pas recommandé),
  • destinée à être utilisée en conjonction avec Android TalkBack ou iOS VoiceOver (par exemple principalement par des personnes malvoyantes)
  • destinée à être utilisée par un lecteur d’écran (= Screen Reader) qui décrira l’application sans avoir à regarder l’écran.

En lisant ceci, on peut se rendre compte à quel point cela pourrait être important si vous désirez que votre application soit aussi utilisable par les personnes malvoyantes…

Comment est-ce implémenté dans Flutter?

Lorsque Flutter restitue, construit l’arborescence des Widgets, il maintient également un second arbre, appelé Semantics Tree, qui est utilisé par la technologie d’assistance de périphérique mobile (Android TalkBack ou iOS VoiceOver).

Chaque nœud de cet Semantics Tree est un **SemanticsNode * qui pourrait correspondre à un ou à un groupe de Widgets.

Chaque SemanticsNode est lié à une SemanticsConfiguration, une série de propriétés qui indiquent à la technologie d’assistance du périphérique mobile comment:

  • décrire le noeud
  • se comporter avec le noeud

SemanticsConfiguration

Décrit les informations sémantiques associées à un SemanticsNode. Voici quelques-unes des propriétés (pour une liste exhaustive, veuillez vous référer à la documentation officielle).

Nom Description
reducedValue la valeur qui résultera de l’exécution d’une action diminution (par exemple, un Slider)
increaseValue la valeur qui résultera de l’exécution d’une action increase (par exemple, un Slider)
isButton le noeud est-il un bouton ou non
isChecked le noeud est-il une sorte de case à cocher, est-il coché ou non
isEnabled le noeud est-il activé ou non
isFocused le noeud est-il actif pour l’introduction d’une information par l’utilisateur
isHeader le noeud est-il un en-tête
isSelected le noeud est-il sélectionné
isTextField le noeud est-il un champ de texte
indice brève description du résultat de l’exécution d’une action sur ce noeud
étiquette description du noeud
valeur description textuelle de la valeur

Widgets automatiquement liés à une Sémantique par Flutter

La plupart des Widgets Flutter sont implicitement liés à un Semantics car ils peuvent tous être utilisés directement ou indirectement par le moteur Screen Reader.

Pour illustrer ceci, voici un extrait du code source Flutter associé à un Button:

class _RawMaterialButtonState extends State<RawMaterialButton> {

  ...

  @override
  Widget build(BuildContext context) {

    ...

    return new Semantics(
      container: true,
      button: true,
      enabled: widget.enabled,
      child: new ConstrainedBox(
        constraints: widget.constraints,
        child: new Material(
          ...
        ),
      ),
    );
  }
}

Comment définir une sémantique?

Parfois, il peut être intéressant de définir (= donner une sémantique) une partie de l’écran afin qu’elle puisse être décrite par la technologie d’assistance des appareils mobiles.

Dans ce cas, utilisez simplement l’un des Widgets suivants comme conteneur de vos sous-widgets:

  • Semantics    quand vous voulez décrire seulement 1 Widget particulier   
  • MergeSemantics    quand vous voulez décrire un groupe de Widgets. Dans ce cas, les différentes Semantics qui seront définies dans le sous-arbre de ce noeud seront fusionnées en une seule Sémantique.    Cela pourrait être très utile pour regrouper la sémantique, cependant, en cas de conflit sémantique, le résultat peut être absurde.

Single Semantics

La classe à utiliser pour définir une sémantique est Semantics. Cette classe possède 2 constructeurs: un verbeux ou un concis.

Voici les deux façons de définir une sémantique, l’explication suit:

@override
Widget build(BuildContext context){
  bool toBeMergedWithAncestors = false;
  bool allowDescendantsToAddSemantics = false;

  return new Semantics(
    container: toBeMergedWithAncestors,
    explicitChildNodes: allowDescendantsToAddSemantics,
    ...(list of all properties)...

    child: ...
  );
}

@override
Widget build(BuildContext context){
  SemanticsProperties properties = new SemanticsProperties(...);
  bool isContainer = toBeMergedWithAncestors;
  bool explicitChildNodes = allowDescendantsToAddSemantics;

  return new Semantics.fromProperties(
    container: isContainer,
    explicitChildNodes: explicitChildNodes,
    properties: properties,
    child: ...
  );
}
Nom Par défaut Description
container faux Si la valeur est vraie (=true) un nouveau SemanticsNode sera ajouté à l’arborescence Semantics, permettant à cette sémantique de ne pas être fusionnée avec la sémantique des ancêtres. Si la valeur est false, cette sémantique sera fusionnée avec la sémantique des ancêtres
explicitChildNodes faux indique si les descendants de ce widget sont autorisés à ajouter des informations sémantiques au SemanticsNode de ce widget

Comment ne pas avoir une sémantique?

Parfois, il pourrait y avoir des cas où vous n’auriez pas besoin de sémantique du tout. Cela pourrait être le cas pour des parties de l’écran qui ne sont que décoratives, pas importantes pour l’utilisateur.

Dans ce cas, vous devez utiliser la classe ExcludeSemantics pour exclure la sémantique de tous les descendants de ce widget. Sa syntaxe est la suivante:

@override
Widget build(BuildContext context){
  bool alsoExcludeThisWidget = true;

  return new ExcludeSemantics(
    excluding: alsoExcludeThisWidget,
    child: ...
  );
}

La propriété excluding (par défaut: true) indique au système si vous souhaitez également que ce Widget soit également exclu de l’arborescence Sémantique.

Comment regrouper les widgets en une seule sémantique?

Dans certaines circonstances, vous pourriez également vouloir regrouper toutes les sémantiques d’un ensemble de Widgets.

Un exemple de base d’un tel cas pourrait être un bloc visuel composé d’un Label et d’un Checkbox, chacun définissant sa propre sémantique. Il serait préférable que si l’utilisateur appuie sur le bloc, la technologie d’assistance du dispositif mobile fournisse une assistance liée au groupe plutôt qu’à chaque widget du groupe.

Dans ce cas, vous devez utiliser la classe MergeSemantics.

AVERTISSEMENT

Soyez très prudents lorsque vous voulez fusionner la sémantique, car si vous avez des sémantiques conflictuelles, cela peut devenir absurde pour l’utilisateur. Par exemple, si vous avez un bloc composé de plusieurs cases à cocher, chacune d’entre elles ayant des statuts différents (cochée et non cochée), le statut Sémantique résultant sera coché, induisant l’utilisateur en erreur.

Comment déboguer la sémantique?

Enfin, si vous souhaitez déboguer la sémantique de votre application, vous pouvez définir la propriété showSemanticsDebugger de votre MaterialApp sur true. Cela forcera Flutter à générer une couche en superposition pour visualiser l’arbre sémantique.

void main(){
  runApp(new MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: new Text('My Semantics Test Application'),
      showSemanticsDebugger: true,
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new FirstScreen(),
    );
  }
}

Conclusions

Comme la documentation officielle n’est pas encore très verbeuse sur ce sujet, je voulais simplement partager ma compréhension avec vous.

J’espère que cette introduction a souligné le fait qu’il est important de considérer la Sémantique si vous voulez mettre une application en production un jour, car les utilisateurs mobiles peuvent activer la technologie d’assistance de l’appareil mobile de leur téléphone et utiliser votre application. Si votre application n’est pas prête pour cette technologie, il pourrait y avoir des risques qu’elle ne puisse pas être utilisée par cette population d’utilisateurs.

Restez à l’écoute pour les prochains articles et bon codage.