Make Apps More Accessible with Flutter’s Semantic Widgets

Make Apps More Accessible with Flutter’s Semantic Widgets
Make Apps More Accessible with Flutter’s Semantic Widgets

A screen reader is a useful feature for users who have poor eyesight, or those who cannot use a touchscreen easily. Such users have to rely on audible alerts and notifications to navigate an app.

One of the best features of Flutter’s semantic widgets is that it allows developers to create apps that cater to users who have different types of visual, age-related, or physical limitations. This improves the user-friendliness and accessibility of your app letting such users be more productive and independent. In this blog, we will understand how these widgets work by creating a screen reader that directs users with audible prompts.

How Semantic Widgets Work in Flutter

The Semantics widget annotates the widget tree with an outline of its child. So, after you want to own a customized description of a couple of widgets, you’ll be able to wrap it with a Semantics widget. For the end-user, this means he or she can be made aware of what action will be performed when an on screen object such as a button is tapped.

Let’s start by testing the Semantic Constructor.

If you wish to form your Semantics object constant, select Semantics from the properties constructor. These properties are null by default. Let’s examine this with an easy example:

FaceImage (file:’$name.png’)

When the user selects Image or Icon in talkback mode, it will only pronounce the ‘Image’ sound by default. To have a custom sound or message, wrap the widget with the Semantics widget and add the label property:

Semantics ( FaceImage(file:’$name.png’, ), label: ‘An image of $name’ )

In the above image, suppose we choose to pronounce ‘$name’ as ‘Bird’. In this case, the talkback/voice-over will read the outline as ‘An image of Bird’.

Simple, right?

That’s the magic of using the Semantics. If we set excludeSemantics to true, the default label will not be read out. In this scenario, the applied label description works perfectly.

Additionally, if you’ve added GestureDetector to your widget, that will not work. Instead, one must add the OnTap property within the semantics and apply identical logic there and the OnTap property of GestureDetector as well.

To understand better, let’s take a simple example: If I tap the button, by default the talkback/voice-over will be read out as “Double-tap to activate”. With this, you’ll be able to exclude semantics to exclude the default instruction. In the label, you’ll include ‘Tap to select’ since we excluded the sentence ‘Double-tap to activate’.

There are 3 Different Categories of Semantic Widgets

  1. Merge Semantics: It merges all available child semantics into one, and therefore, the screen reader will handle all of them without any delay.
  2. Exclude Semantics: It omits a subtree from the semantics tree that is redundant and not that important to the user.
  3. Indexed Semantics: Helps to keep track of the relevant information that is passed to screen readers.
ListView.builder(
itemCount: 5,
addSemanticIndexes: false,
semanticChildCount: 3,
itemBuilder: (context,position){
return MergeSemantics(
child: Semantics(label: 'Container with size 200 and red background',
enabled: position == 1 ? true : false,
selected: position == 0 ? true : false,
child: Container(color: Colors.red, height: 200, width: 200,
child: Column(
children: [
Text('First container text is item $position'),
BlockSemantics(child:Text('Second container text is item $position')),
ExcludeSemantics(child:Text('Third container text is item $position')),
Text('Fourth container text is item $position'),
],),),),);
}
);

>> MergeSemantics

In the above example, MergeSemantics is added as a root widget to the semantics widget. Consequently, it will merge all the child semantics into one and the screen reader will read everything at once. Do note that It’ll read out the semantic label, whether it is enabled or not, and whether it is selected or not. It’ll also read out the text widget.

So, we’ve created the list view with 5 items, it’ll disable all the elements except the second item. This is because we’ve applied the enabled property based on position. The same applies to the selected property, it’ll set the chosen state to the primary element.

>> ExcludeSemantics & BlockSemantics

We have used BlockSemantics for the second child within the container in the above examples. Therefore, the widgets before this node are omitted and not read by the screen readers. If you prefer to exclude any item from the container, you must use ExcludeSemantics, similar to how it has been applied to the third item of the container in the above example.

Let’s run the application and click on the primary element. Here, the screen reader should say: “Container with size of 200 and red background second container text is item 0, fourth container text is item 0.”

>> IndexedSemantics

Indexed Semantics is used to read out relevant information in the scrollable widget like Listview(as shown above).

Dividers within the ListView, Talkback/voice-over will not read out the divider. Also, which item description to be readout next can also be specified using the index property.

ListView(semanticChildCount: 2, 
children: const <Widget>[
IndexedSemantics(index: 1, child: Text('First')),Spacer(),
IndexedSemantics(index: 0, child: Text('Second')),Spacer(),
],);

Let’s work with an example using Semantic.fromProperties to understand it better.

Instead of providing details directly inside the Semantics widget, we can initialize the value first, later setting the values to properties.

ListView.builder(
itemCount: 5,
addSemanticIndexes: false,
semanticChildCount: 3,
itemBuilder: (context,position){
final String semanticsLabel = 'Container with size 200 and red background';
final String enabled: position == 1 ? true : false,
final String selected: position == 0 ? true : false,
final SemanticsProperties properties = SemanticsProperties(label:semanticsLabel,enabled:enabled,selected:selected);
return MergeSemantics(
child:Semantics.fromProperties(properties: properties, excludeSemantics: true, child:
child: Container(color: Colors.red, height: 200, width: 200,
child: Column(
children: [
Text('First container text is item $position'),
BlockSemantics(child:Text('Second container text is item $position')),
ExcludeSemantics(child:Text('Third container text is item $position')),
Text('Fourth container text is item $position'),
],),),),);
}
);

Hope the above examples and insights on various types of Semantic widgets in Flutter help you create user-friendly and accessible mobile applications. Thank you for reading!

Flutter is a registered trademark of Google LLC.

Author — Pradeep Kumar, DLT Labs

About the Author: Pradeep is an experienced mobile app developer who has a track record for creating successful apps in Android, iOS, and Flutter that are both well-received and commercially viable.

Disclaimer: This article was originally published on the DLT Labs Blog page:
https://www.dltlabs.com/blog/make-apps-more-accessible-with-flutters-semantic-widgets-224385

DLT Labs Logo
DLT Labs Logo

DLT Labs is a global leader in Distributed Ledger Technology and Enterprise Products. To know more, head over to: https://www.dltlabs.com/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store