We’ve gone over a lot of issues that can be revealed mainly by Android Scanner or by using a keyboard for navigation. Now, with this article (the third of our four-part Android series), we’d like to continue with a11y issues that can be recognized by a screen reader — in our case, TalkBack.
State Announcement
Basically, you can (and should) announce everything that happens on the screen. Here are some things that should be announced by TalkBack:
Entering a Screen
This is something taken from a Google Play app where they announce that the detail of an app was opened. The easiest way would be to announce the Toolbar title since it would be sufficient information in most cases. You may sometimes need more information, e.g., when you open Profile Detail; you can then add a user name and make the announcement dynamic.
Example with SettingsFragment.kt:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireView().announceForAccessibility("Settings")
}
Progress States
There may be cases where you show a progress bar while sending an event to the server and waiting for a response. This is pretty straightforward for users who can see what is happening on the screen, but users using TalkBack would not have any idea about the current state.
Display and announce the loading state while fetching records from the server:
buttonApply.setOnClickListener {
progressView.visibility = View.VISIBLE
progressView.announceForAccessibility("Loading more records"))
...
progressView.visibility = View.GONE
progressView.announceForAccessibility("More records loaded")
}
Error States
The best thing would be to not use any custom errors but rather errors that are a part of the TextInputLayout view or any similar views. But in the real world, we do need to show a custom error from time to time. When doing this, we have to make sure to also announce this error to users via TalkBack.
And one more common mistake related to errors: A lot of applications use Toast or Snackbar views for displaying errors. This breaks another WCAG requirement which states that users should have enough time to read the content. Depends how far you want to go but, with that being said, those self-dismissable views are not compliant for such information.
Here is an example of a custom Button view capable of displaying an error. You can manually announce the error using announce ForAccessibility
or you can use sendAccessibilityEvent
with event TYPE_VIEW_ACCESSIBILITY_FOCUSED
to ensure that the error is spoken by the screen reader:
fun setError(error: String) {
error.text = error
error.visibility = View.VISIBLE
error.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
)
}
Selections
When any selection happens on a screen, it should also be communicated to the user, e.g., selecting a date in the calendar picker can be announced once the user clicks on the Select button.
In a dialog where an option is selected and value is passed to parent:
buttonOptionOne.setOnClickListener { button ->
button.announceForAccessibility("Option 1 selected.")
dismiss()
}
Other Screen States or Actions
Other actions that should be announced could be delete, update or create of any record.
buttonDeleteRecord.setOnClickListener { button ->
button.announceForAccessibility("Record deleted.")
finish()
}
Screen Order
While using any screen reader, the user may not be aware of disclaimers that are below actionable items.
Here is an example of a disclaimer that is placed below the Submit button. When a user navigates through the screen with any screen reader, it first reads the Submit button and the user may not get to the disclaimer at all before pressing the button.
Grouping Views Together
Some views do not make sense when read alone by TalkBack, e.g., when you have a TextView 'Start date' and then a second TextView with the actual date. Or a Profile item in your settings screen with Profile Photo, UserName and Email. Even list items inside RecyclerView can benefit from being grouped into one view. This is fairly simple, as all you have to do is to put those views into some ViewGroup and make the group focusable: android:focusable="true".
That way, the whole ViewGroup would be presented simultaneously by TalkBack and all its children views' descriptions will be read together — therefore making more sense.
For API level 28 and higher, you can use the android:screenReaderFocusable=true
property for ViewGroup and set android:focusable=false for child views
.
Example of two TextViews that are grouped together in LinearLayout for better screen reader result:
<LinearLayout
android:focusable="true">
<TextView
android:text="Start date" />
<TextView
android:text="10/10/2021" />
</LinearLayout>
Grouping views together here results in way better reading. Now, when a user navigates to the Start Date view, the screen reader says “Start date Saturday May first.”
Headings
TalkBack has many options and one of them is to jump through all Headings present on a given screen. A screen can be broken into multiple parts that are clear at first glance, but only as soon as you see them, right? So, what we can add to our titles or subtitles to make it even easier to navigate through the screen is a simple property in the layout file: android:accessibilityHeading="true"
. This ensures that when the user changes the read settings of TalkBack to Headings, it will iterate through all Headings you set.
Based on our experience, setting up the title of the custom Toolbar view as a Heading makes a lot of sense. Or sections in Settings may be good examples for using headings.
Example of setting a TextView as heading for given screen:
<TextView
android:text="Important Title"
android:accessibilityHeading="true" />
Labels and Live Region
There is a view property android:labelFor="@+id/slider"
that can be used to pair elements where one describes the other, e.g., a slider that has a custom TextView showing its value. You can then simply add this property to the TextView to specify the view for which the TextView serves as a label.
A similar property that could sometimes be handy is android:accessibilityLiveRegion="polite"
, which allows you to automatically notify about view changes. If any view is changed on the screen based on any circumstances, this property can be used for announcing.
Read Order
Yes, even the read order of views can be changed easily by two properties: android:accessibilityTraversalBefore="@id/xyz"
and android:accessibilityTraversalAfter="@id/xyz"
. Most of the time, your view would be nicely built from top to bottom and also will be read in that order by default. There are some cases where the screen reader has a different order; in that case, you can use those properties.