Home Android & Kotlin Books Android Accessibility by Tutorials

10
Robust Written by Victoria Gonda

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

People use their devices in many different ways, so you need to make sure your app is compatible with accessibility services.

While much of the work happens automatically or is trivial to implement, you need to put in effort for custom views. That’s the main focus of this chapter.

You’ve explored perceivable, operable and understandable. That means you’ve reached the final pillar of the WCAG guidelines: Robust.

Robust: Content must be robust enough that it can be interpreted by a wide variety of user agents, including assistive technologies.

A robust app is one that people can access in various ways, including with different assistive technologies, such as screen readers.

Android does a lot of the heavy lifting by providing components with built-in support. And it provides an interface for you to leverage. In this chapter, you’ll learn how to use these built-in tools to improve your apps by providing more information about views.

Success Criterion 4.1.2 Name, Role, Value: For all user interface components (including but not limited to: form elements, links and components generated by scripts), the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.

Level A

This criterion may sound daunting. And you’re not wrong. However, this chapter will give you the insights and practice you need.

You can either work on the starter project for this chapter or continue with the project you’ve used earlier in the book.

Relying on system views

The simplest way to satisfy the success criterion is by using the views that Android provides. They typically include everything — or almost everything — you need to inform accessibility services about a view’s role and content.

In other words, if you can use a system view instead of creating a custom view, do that! You can customize it; for example, if you need a custom button, you extend the Button rather than starting from scratch with a View.

In Taco Tuesday, you can take advantage of a system view to improve the recipe details screen. There’s a Made it checkbox that is currently two different views: the label and the check box.

Made recipe checkbox.
Made recipe checkbox.

Turn on TalkBack, run the app and observe how this view behaves with the screen reader.

You must highlight them separately. There’s no indication that the checkbox belongs to the “Made recipe” label.

Made recipe TalkBack reading.
Made recipe TalkBack reading.

You can improve this by including the text of the label as part of the checkbox.

Open fragment_recipe_detail.xml. Delete the label:

<TextView
 android:id="@+id/recipe_detail_made_it_label"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@string/recipe_detail_made_recipe" />

You’re deleting the old view because you’ll combine these views,

You also need to delete references to this label in the Kotlin code:

  1. Open RecipeDetailFragment.kt.
  2. Delete the recipeDetailMadeItLabel.visibility = View.VISIBLE line in showEditableFields().
  3. Delete the recipeDetailMadeItLabel.visibility = View.GONE line in hideEditableFields().

Then, in fragment_recipe_detail.xml, add the text of the label to the CheckBox with ID recipe_detail_made_it:

android:text="@string/recipe_detail_made_recipe"

Now, the CheckBox owns the label and can inform the accessibility services about the associated label.

Build and run. Use TalkBack again and notice that the checkbox is correctly labeled.

Made recipe checkbox with updated TalkBack.
Made recipe checkbox with updated TalkBack.

To make stylistic changes to the CheckBox or any other view, modify or extend the CheckBox itself. Don’t create something new. Using existing views allows you to leverage Android’s built-in support assistive technologies.

Indicating a view’s role

Although the system’s components will support most accessibility services without any intervention, you may need to make adjustments to achieve the desired experience. In many cases, you can use an accessibility delegate to make these modifications.

<string name="banner_role">banner</string>

Experimenting with delegates

Next, you’ll experiment with a delegate for the banner that displays across the top of the screen.

class BannerAccessibilityDelegate : AccessibilityDelegateCompat()
override fun onInitializeAccessibilityNodeInfo(
  host: View?,
  info: AccessibilityNodeInfoCompat?
) {
 // 1
 super.onInitializeAccessibilityNodeInfo(host, info)
 // 2
 info?.roleDescription =
   host?.context?.getString(R.string.banner_role)
}
ViewCompat.setAccessibilityDelegate(
  binding.mainBanner,
  BannerAccessibilityDelegate()
)
TalkBack reading banner role.
WuttCunm kaiyojf cubtaq sugi.

Building custom views

Custom views can become incredibly complex with different touch areas, actions and behaviors. You need to communicate this complexity to the accessibility services. To make your task a little trickier, documentation around these use cases is a bit…sparse.

Nacho and spice rating bars.
Xecho uzq jheba vodinf mulx.

Rating bar TalkBack selection.
Celijw gim DoslFolf towewveor.

Using ExploreByTouchHelper

If you research how to create these virtual views, you’ll find many options. This chapter will teach you how to use an ExploreByTouchHelper, a type of accessibility delegate that can help you define touch areas.

inner class CustomRatingBarExploreByTouchHelper(host: View) :
  ExploreByTouchHelper(host) {
}

Defining virtual views

The first things you’ll define are:

override fun getVisibleVirtualViews(
  virtualViewIds: MutableList<Int>?
) {
 rectangles.forEachIndexed { index, _ ->
  virtualViewIds?.add(index)
 }
}
override fun getVirtualViewAt(x: Float, y: Float): Int {
 // 1
 val index = findRatingAtPoint(x, y)
 // 2
 return if (index == INVALID_VALUE) INVALID_ID else index
}
override fun onPopulateNodeForVirtualView(
  virtualViewId: Int,
  node: AccessibilityNodeInfoCompat
) {
 // 1
 node.text = context.getString(
   R.string.custom_rating_bar_description,
   label,
   virtualViewId + 1
 )
 // 2
 node.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK)
 // 3
 node.setBoundsInParent(rectangles[virtualViewId])
}

Performing actions

Finally, when someone initiates a click action, regardless of if it was with a physical tap or through an accessibility service, you need to add logic to handle it correctly.

override fun onPerformActionForVirtualView(
  virtualViewId: Int,
  action: Int,
  arguments: Bundle?
): Boolean {
 when (action) {
  AccessibilityNodeInfoCompat.ACTION_CLICK -> {
   onSelected(virtualViewId)
   return true
  }
 }
 return false
}

Linking the view and delegate

Now you’re ready to hook up the delegate that defines your virtual views to your custom view.

private val exploreByTouchHelper = CustomRatingBarExploreByTouchHelper(this)
ViewCompat.setAccessibilityDelegate(this, exploreByTouchHelper)
override fun dispatchHoverEvent(event: MotionEvent?): Boolean {
 return (event?.let {
  exploreByTouchHelper.dispatchHoverEvent(it)
 } ?: run { false }
   || super.dispatchHoverEvent(event))
}

override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
 return (event?.let {
  exploreByTouchHelper.dispatchKeyEvent(it)
 } ?: run { false }
   || super.dispatchKeyEvent(event))
}

override fun onFocusChanged(
  gainFocus: Boolean,
  direction: Int,
  previouslyFocusedRect: Rect?
) {
 super.onFocusChanged(gainFocus, direction,
   previouslyFocusedRect)
 exploreByTouchHelper.onFocusChanged(gainFocus, direction,
   previouslyFocusedRect)
}
TalkBack nacho rating selection.
RuzxJinm togpo puwitn wefowcoac.

Improving the state

When using TalkBack, you can’t discern a recipe’s current rating or know when it changes. This diminishes the experience, so you’ll set the content description to the current rating when a user rates a recipe.

<string name="current_rating_description">Current rating is %d</string>
contentDescription = context.getString(R.string.current_rating_description, value)
accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_POLITE
Current rating announced.
Fuwwesx moyosq asgiimroq.

Seeing service limitations

While working through this book, you’ve probably noticed that different devices and Android builds support different accessibility services. The differences go deeper than that.

Challenges

Challenge 1: Fix the rating bar

The rating bar is editable when on the details screen and not editable when in the list view.

Key points

  • A robust app is one that integrates with accessibility services.
  • System views are the most reliable way to support accessibility across devices.
  • An accessibility delegate is one of the ways to communicate details to accessibility services.
  • Use ExploreByTouchHelper to create and manage virtual views when a custom view has multiple touch targets.
  • Accessibility services often behave differently on different devices.
  • Android is making continuous improvements to accessibility services.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.