Having spent the last ten years or so as an avid Apple accessibility user (I’m legally blind) and iOS developer, I’m pretty happy with the current state of assistive technology when it relates to my platform of choice. Every year, more progress is made across Apple’s various universal access tools and in addition, they do a very thorough job of making it easy for app developers to support their efforts by constantly evangelizing the idea of accessibility and providing great framework support with quality documentation. In short, the accessibility story on Apple platforms is pretty complete.

Now, based on the title of this post, I’m going to assume you, the reader, are an Android developer. At this point, you’re probably wondering why I spent an entire paragraph extolling the virtues of iOS accessibility, and the answer is simple. When it comes to accessibility, the market and mindshare belong to Apple.

Graph from webaim.org screen reader survey

The above data from a 2015 survey that polled blind and visually impaired users about their technology usage shows a pretty stark trend, while Android comes in a very clear second with 20%, it is still grossly out represented by Apple, coming in at nearly 70%.

I present this data and my own personal opinion not to denigrate Android as a platform, my reasoning is quite the opposite actually. Recently I was given the fantastic opportunity to immerse myself in Android development, spending a total of six months learning and developing on a project that had tight ADA (Americans with Disabilities Act) requirements. The above chart is proof that we as Android developers must do better, and I’m here to tell you that, contrary to what you may know or believe, Google has given us the tools and technology to provide a first-class accessibility experience, it’s simply up to us to take action. What follow are a series of best-practices, tips, and references that will help you ensure your app is fully accessible.

Before you dig in, you should know that for the purpose of this post, “accessibility” primarily covers accessing Android via Talkback as a visually impaired user. There are a broad array of other disabilities and associated technologies on Android and other platforms whose users benefit from following many of these guidelines. Still, some of this content may apply only to visual disability, as this is obviously the area in which I’m most comfortable.

Required reading

Before digging into the rest of this post, you should know that there are a number of other great accessibility resources worth having a look at. The Android Accessibility Overview provides a great high-level introduction and is a great place to start. You might also want to check out the end user documentation for the various Android accessibility features for a better understanding of how a disabled person might actually interact with their device. Finally, I would highly suggest going into the accessibility settings on your Android device, enabling Talkback, and then playing with some of your favorite apps to get a first-hand look.

Specify Content Description, On Everything

The first, and most important tool in your Android accessibility toolbox is content description. Almost every UI component exposes this attribute (in fact it is a property on View ), set it in layout files via android:contentDescription or directly in code by calling view.setContentDescription() . The API documentation for this attribute states the following:

A content description briefly describes the view and is primarily used for accessibility support to determine how a view should be presented to the user. In the case of a view with no textual representation, such as ImageButton, a useful content description explains what the view does. For example, an image button with a phone icon that is used to place a call may use “Call” as its content description. An image of a floppy disk that is used to save a file may use “Save.”

The good news is that many components provide default implementations of this attribute, particularly in situations where it is easy to derive a sensible value. Things like text views, buttons containing text, or text inputs are for the most part already accessible out of the box. The better news is that by simply taking a few extra seconds to tag the views that are not automatically accessible (such as image views, image buttons, etc), will result in your app usually being 90% accessible.

Providing sensible content description gets you a lot of bang for the buck, so try and pay it some mind as you create your interfaces. And, remember to source the strings you set for this value from localizable strings files as you would any other UI string (turns out there are visually impaired people that speak languages besides English).

Improve Inputs Using “labelFor”

Despite the fact that many components provide sensible values for content description, there are some situations where the user can benefit greatly from some additional context. One of these scenarios is the usage of text views as labels for other elements on screen. A sighted person typically has visual cues that help them determine when a label is related to another element, however without additional information, a screen reader like Talkback will simply read components in linear succession, resulting in a good deal of ambiguity.

Luckily, Android has a View attribute that helps resolve this issue called labelFor . From the docs:

Sets the id of a view for which this view serves as a label for accessibility purposes.

The labelFor attribute should be used in any situation where it makes sense to associate a label with some control.

<LinearLayout android:orientation="horizontal"> <TextView android:id="@+id/textView2" android:text="@string/billing_info_address_same_as_shipping" android:labelFor="@+id/switch_address_same_as_shipping" /> <Switch android:id="@+id/switch_address_same_as_shipping" /> </LinearLayout> 1 2 3 4 5 6 7 8 9 <LinearLayout android : orientation = "horizontal" > <TextView android : id = "@+id/textView2" android : text = "@string/billing_info_address_same_as_shipping" android : labelFor = "@+id/switch_address_same_as_shipping" /> <Switch android : id = "@+id/switch_address_same_as_shipping" /> </LinearLayout>

In the above example we have a horizontal linear layout with a label and a switch. A sighted user would see this layout and Infer that the text preceding the switch is its’ label. To help a non-sighted user make this same association, we set the labelFor attribute on the TextView to the switch ID which will result in Talkback associating the two elements.

Hide elements that don’t add value

It is often the case that we adorn our UI with elements that are purely present to provide visual flare. Elements that do not provide any clear value to a non-sighted user can be hidden from screen readers via the use of the android:importantForAccessibility attribute. Described by the doc as:

Describes whether or not this view is important for accessibility. If it is important, the view fires accessibility events and is reported to accessibility services that query the screen. Note: While not recommended, an accessibility service may decide to ignore this attribute and operate on all views in the view tree.

It is worth noting that the value for this attribute can be a number of values other than simply “yes” or “no,” however in most cases you’ll be using those values (see the full documentation for more info).

Ensure that you give a good deal of thought to which views can be hidden from accessibility. Modern mobile UI tends to make use of subtle cues to instruct intended user behavior. While it may seem logical to hide what amount to purely visual views, if they are functionally relevant you should consider alternative approaches to conveying the same information to accessibility users.

Group Elements Using Focusable Containers

The default accessibility behavior for views in any layout is for them to be interpreted and read separately as a user navigates through the layout. While this is fine for the vast majority of cases, there are some situations where a UI component is composed of several smaller components, and it would make more sense to express the content as a single accessibility element.

Fortunately Android provides a very easy way to achieve this behavior, by using the focusable attribute of View . According to the documentation:

Set whether this view can receive the focus. Setting this to false will also ensure that this view is not focusable in touch mode.

Note that the description mentions nothing about accessibility, however Talkback infers accessibility behavior based on this attribute regardless. When you have a deep view hierarchy, setting this value to “true” will cause Talkback to treat that entire hierarchy as one element, using its content description only.

Expose Transient Information Via Announcements

Often times, an app uses subtle visual cues to inform a user of some non-static change to the state of the UI. Many of the default Android UI elements that are transient (progress bars, etc) already provide good accessibility support, but for situations where you might be implementing a custom control or any other element that represents dynamic state, Android allows you to issue announcements that sound only if Talkback is enabled. From the doc for announceForAccessibility:

Convenience method for sending a TYPE_ANNOUNCEMENT AccessibilityEvent to make an announcement which is related to some sort of a context change for which none of the events representing UI transitions is a good fit. For example, announcing a new page in a book. If accessibility is not enabled this method does nothing.

Be aware however, that triggering announcements may interrupt other default announcements and should therefore be used very sparingly and only for situations where there is absolutely no alternative.

Provide More Context Using Accessibility Events

In most cases, the default accessibility behavior is sufficient for informing things like focused elements, navigation ordering, etc. Sometimes however, it makes sense to take more direct control over the accessibility mechanism. For very direct control over accessibility, Android exposes a rich set of events via AccessibilityEvent.

You can use code like the following to issue events:

view.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION); 1 view . sendAccessibilityEvent ( AccessibilityEvent . CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION ) ;

Issuing events directly can be useful for things like refocusing the accessibility cursor on a specific element or notifying the user that an element content has changed. Additionally, if you are creating a completely custom view, you will need to issue the appropriate events in order to make your control accessible.

Putting It All Together

Now that I’ve gone through a number of techniques and best practices, let’s look at how we might implement accessibility for a non-trivial situation. The following is a screenshot from the Amazon Shopping app, note the cart button in the upper right. Whenever a user ads an item to their cart, the item count increments, and the user can tap on it at any time to go to their cart.

Ignoring how this UI behaves in the actual Amazon app, let’s assume we’re implementing it from scratch and walk through how we might do it, then discuss what we’d have to do to make it accessible. Let’s start with a basic layout like the following:

<FrameLayout> <ImageView android:src="@drawable/ic_cart" /> <TextView android:id="@+id/textview_cart_count" android:text="2" /> </FrameLayout> 1 2 3 4 5 6 7 8 9 10 <FrameLayout> <ImageView android : src = "@drawable/ic_cart" /> <TextView android : id = "@+id/textview_cart_count" android : text = "2" /> </FrameLayout>

We can also assume that there is code somewhere that programmatically updates the “textview_cart_count” whenever the value changes.

For a sighted user, this implementation works beautifully, but for someone that can’t see the screen, coming at this UI via Talkback would be less than ideal.

FrameLayout will be ignored by Talkback as it is a container element, as will ImageView because no content description is provided. This results in the count TextView being the only element read.



The text count “2” will be read with no additional context.



Since there isn’t a Button type being used, the user will have no idea that the element is actionable.



Because the container element is ignored, the area on which Talkback is activated will only be the TextView bounds itself (likely much smaller than the overall element).



When someone adds an item to their cart, they won’t see the number change in real time and therefore have no reminder of how many total items they have.



Fixing all of these issues can be done with a few of the techniques mentioned earlier.

First, let’s better express the intent of this entire element by grouping the layout using focusable.

<FrameLayout android:focusable="true"> ... </FrameLayout> 1 2 3 <FrameLayout android : focusable = "true" > ... </FrameLayout>

Awesome, now Talkback will treat the entire layout as one element. But in doing so, we’ve now lost the content description from the count text view. Luckily, the default content description provided by TextView in this case was horribly inadequate anyway, so let’s do better.

We need to set the content description of the FrameLayout now that it is the accessibility element. There is already code that is updating the text view any time this count changes, we simply add a bit more to dynamically generate the content description as well.

void updateCartCount(int count) { String countString = String.valueOf(count); // Update the count text view textViewCart.setText(); // Update the containing layout content description // NOTE: cart_count_accessibility_desc is defined as "%d items in cart. link" String contentDesc = getString(R.string.cart_count_accessibility_desc, countString); layoutCart.setContentDescription(contentDesc); } 1 2 3 4 5 6 7 8 9 10 11 void updateCartCount ( int count ) { String countString = String . valueOf ( count ) ; // Update the count text view textViewCart . setText ( ) ; // Update the containing layout content description // NOTE: cart_count_accessibility_desc is defined as "%d items in cart. link" String contentDesc = getString ( R . string . cart_count_accessibility_desc , countString ) ; layoutCart . setContentDescription ( contentDesc ) ; }

In the above code, we amend the code that originally just set the text view count to also update the layout content description. The content description includes the count, more context, as well as a hint regarding the triggered action (it behaves like a link). In most cases, Android will correctly identify the element type, but since we’re not using a standard button here, rather a clickable layout, it helps to provide that hint explicitly. Note that we pull the string out of a localizable strings file (also note that I’m not handling plural/singular here for the sake of brevity, you should take the time to do it for production code).

Finally, we still need to handle informing the user that the cart count has been updated. Probably the best option for achieving this is to throw up a Snackbar informing the user that they have added an item to their cart, Snackbars are handled well by Talkback out of the box, so they are a solid option, but unless you want to reinforce the count to your sighted users, the Snackbar text will likely not contain the updated count.

There are a few possible ways to handle this situation, One option would be to check if the user has Talkback enabled, and if so, amend the snack bar message with the updated count:

void showCartSnackbar() { String message; ... AccessibilityManager am = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE); boolean isAccessibilityEnabled = am.isEnabled(); if (isAccessibilityEnabled) { message += " " + getStrings(R.strings.cart_snackbar_count, count); } ... } 1 2 3 4 5 6 7 8 9 10 void showCartSnackbar ( ) { String message ; . . . AccessibilityManager am = ( AccessibilityManager ) getSystemService ( ACCESSIBILITY_SERVICE ) ; boolean isAccessibilityEnabled = am . isEnabled ( ) ; if ( isAccessibilityEnabled ) { message += " " + getStrings ( R . strings . cart_snackbar_count , count ) ; } . . . }

Like most things in software, there is no single right answer to this particular problem, again, just put yourself in the mindset of the accessibility user and ensure that even minor visual queues you might take for granted are conveyed in some non-visual way.

And that’s it, our cart button is now fully functional for sighted and non-sighted users alike. While it may seem like a good deal of extra work, the more you do it, the easier it will be to identify cases where it is needed, and the more second-nature going through the motions will become.

Make Sure It All Works

Spending the time needed to add accessibility support throughout your app is a great first step towards making your user experience available to everyone. However, just as you would never ship a new feature before rigorously testing it, so should you verify your accessibility-focused improvements.

The most obvious and quickest way to do this is by enabling Talkback on your device and running through the major flows of your app. Try and put yourself in the mindset of someone that can’t see the screen, intentionally looking elsewhere and navigating with just touch. You’ll find that screens or flows which may have made perfect sense when you could see the entire UI, aren’t workable as an non-sighted user, even in situations where you’ve properly adhered to good accessibility guidelines.

Wrapping Up

The state of mobile software in terms of accessibility is better than it has ever been and getting better with every major OS release. While iOS jumped out initially to a very strong lead, the gap is narrowing, and it is way past due for the Android community to start prioritizing accessibility users in a deeper, more meaningful way. I hope this post has demonstrated that the tools for making your Android app a great user experience for everyone are readily available and that you’ll take this knowledge and apply it to your own app.

If you enjoyed this post, you can follow me on Twitter @nickbona, where I talk about accessibility, technology, and other related topics. Feel free to reach out to the team at Raizlabs, if you’re interested in pursuing an accessibility project!