Hide chapters

Android Accessibility by Tutorials

Second Edition · Android 12 · Kotlin 1.6 · Android Studio Chipmunk

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

Section I: Android Accessibility by Tutorials

Section 1: 13 chapters
Show chapters Hide chapters

Section II: Appendix

Section 2: 1 chapter
Show chapters Hide chapters

2. Hello, Accessibility!
Written by Victoria Gonda

One of the best ways to learn is to jump right in and start applying your new knowledge. Throughout this book, you’ll be working on making an app more accessible. You’ll be able to apply the techniques you learn here right away, whether you’re working on an existing app or building a new one outside of this book. You don’t even need to finish the book to start applying the new information from each chapter in your projects!

You’ll soon see that you already have the tools to get started and that it takes only a couple of lines of code to make a big difference for your users.

Introducing Taco Tuesday

Taco Tuesday is the name of the app you’ll develop as you work through this book. It allows you to discover and save new recipes to enjoy on your next Taco Tuesday. Once you’ve found a recipe you like, you can indicate that you made it, rate it and add notes.

Find the starter project in the materials for this chapter and open it in Android Studio. Build and run the project.

You’ll see that there are auto-advancing slides followed by recipe cards at the onboarding stage. You can swipe the recipe cards to the right and left to save for later or pass them up, respectively, and move to the next recipe.

On-boarding screenshot next to a discover screenshot.
On-boarding screenshot next to a discover screenshot.

Tap Favorites to see the saved recipes. To see a details screen that shows all the ingredients and instructions for a certain recipe, tap the View icon on the recipe card. From here, you can add your rating and notes if you’ve saved the recipe.

Recipe list screenshot next to a details view screenshot.
Recipe list screenshot next to a details view screenshot.

Finally, you’ll see some pop-up dialogs and a bare-bones settings screen.

Screenshot of pop-up next to a screenshot of the settings screen.
Screenshot of pop-up next to a screenshot of the settings screen.

You may have noticed the app has several unpleasant features. The pop-ups are disruptive and the on-boarding process is unclear.

As you improve the app’s accessibility, you’ll also improve the overall user experience. When you complete this book, the app will look more like this:

Screenshots of final discover, list and details views.
Screenshots of final discover, list and details views.

So much nicer! Without further ado, it’s time to dig in and start improving this app!

Improving Accessibility Through Linters

Do you feel lucky? Well, you are! Not only do you have this book in your hands, but you probably already use one of the tools that can make your app more accessible: lint. Android linters include a category that’s all about supporting accessibility.

List of linters in Android Studio.
List of linters in Android Studio.

In most cases, Android Studio enables the lint rules by default. Before you proceed, though, make sure that they are really enabled by following next steps.

In Android Studio, go to Preferences ‣ Editor ‣ Inspections.

Pick the right profile at the top — there are some options:

  • If you choose Project Default, you can check your lint rules into version control.
  • Otherwise, if you pick Default, they will be your local rules. You can create more profiles if needed.

For this project, it doesn’t matter which profile you use, so just pick one.

By default, there are two lint profiles to choose from.
By default, there are two lint profiles to choose from.

Review the list of items below the profile selection. Look for Android ‣ Lint ‣ Accessibility. Select the checkbox at the end of each item.

Check each accessibility linter.
Check each accessibility linter.

On the right side of the Preferences window, you can set the lint severity. Select each accessibility lint and make sure the severity is Warning or Error.

Warning severity.
Warning severity.

Click OK to save the lint settings and close Preferences. Now you’re all set to see lint errors.

Seeing Lint Errors

You have some options for how to view lint errors. The first way is to use Gradle in the command line. Run this command if you’re on Linux or Mac:

./gradlew lint

Or this one for Windows:

gradlew lint

Note: You can use the terminal window in Android Studio under View ‣ Tool Windows ‣ Terminal.

It might take a few minutes to complete. When it’s done, you’ll see in the output: Wrote HTML report to <filename>. Open that file in your browser to see a list of lint warnings.

Scroll down until you see Usability, Accessibility, and Internationalization. These are the warnings that matter for this book.

Note: If you see lint errors as raw text, make sure that filename has the .html extension.

List of linter warnings.
List of linter warnings.

To learn more about each warning, including its location in the code, click the hyperlink. Start by selecting ContentDescription.

The second way to see the results is right in your code. In the HTML output, you can see that contentDescription is missing from fragment_discover.xml. Go to that file and look at the ImageView named discover_button_discard. Hover your mouse over the ImageView text.

Missing contentDescription attribute on image.
Missing contentDescription attribute on image.

Phew! This is a bit of a mess with all these warnings. Good thing you’re here to fix it! The Android system does much of the work of supporting accessibility for you. Because of that, many of the changes you need to make take only a line or two of code.

Understanding content descriptions

That contentDescription warning is one of the accessibility components. But what does it mean?

Assistive technologies, such as screen readers, use content descriptions to communicate what’s on the screen to a user. The system already knows how to read many views. For example, it will read the text for a TextView. Images are much harder to infer without your help.

By adding a contentDescription to an image or image button, you help the user know what the item is and its purpose. In short, this description will literally tell the user it’s a “Save button”, so the user knows how to save their changes. Without a contentDescription, the user would hear “unlabeled button”. You’ll learn more about these in Chapter 3, “Testing & Tools”.

Details description vs missing contentDescription.
Details description vs missing contentDescription.

There are quite a few descriptions to add. You’ll get your hands dirty with them over the next several sections.

Adding content descriptions

Open fragment_discover.xml. The first element you’ll fix is the topmost ImageView with the contentDescription warning that has the ÍD discover_button_discard. This is the thumbs-down button on the first screen that you tap to pass on a recipe.

For icons that act like buttons, like this one, it’s best to use a single verb to describe the view. For this view, use Discard because you’re discarding the recipe to look at the next. Lucky for you, there’s a String resource named shared_discard in use throughout the app!

Add this contentDescription attribute to the ImageView with the ID discover_button_discard:


This makes it so that accessibility services, and the people who use them, know to use this button to discard a recipe.

Notice that the description doesn’t contain the word button. The system can decipher when a component is clickable. The system communicates that state to the user. As a rule, you don’t need to use words like “button” or “label” in your descriptions.

One line makes a difference to the user experience!

In fragment_discover.xml, add the following to the ImageView with the ID discover_button_try:


Here you’re setting a content description for the Try it image.

Good work! That’s one more view that accessibility services can correctly interpret. Now the user will understand this button saves the recipe to the “Try it” list.

Run the linter again and you’ll see two fewer warnings — and you only needed two little lines!

Adding More Content Descriptions

You’re a pro at this now! See if you can work through the following content descriptions with minimal instruction. If you get stuck, check the solution in the final project for this chapter.

Find the views in the following files and add the given content descriptions:

  • In fragment_recipe_detail.xml, add the content description @string/shared_try_it to the ImageView with ID recipe_detail_try_discard_button.

  • Likewise, in item_try_it_recipe.xml, add the content description @string/shared_details to the ImageView with the ID item_recipe_details.

Great! You took care of two more views.

For now, make this your practice: Pick a verb for a button that performs an action. For other images, follow the pattern object + action + context. For example: “A person eating a taco on a Tuesday”.

In Chapter 4, “Perceivable — Layout & Labeling”, you’ll learn more about writing good content descriptions.

Programmatically Adding Content Descriptions

Sorry to inform you, but there’s one issue with your changes to recipe_detail_try_discard_button. Depending on whether you’ve saved the current recipe to try later or not, this view toggles between “Try it” and “Dismiss” in its behavior. This means you also need to toggle the content description. Next, you’ll implement a bit of Kotlin code to fix this issue.

As with most view attributes, you can set the contentDescription programmatically. Go to RecipeDetailFragment.kt to set this view. Find the two methods — showEditableFields() and hideEditableFields().

At the top of each of these, you already have the image set to thumbs-up or thumbs-down on recipeDetailTryDiscardButton. This is where you’ll set the content description.

At the top of showEditableFields(), add the following content description:

recipeDetailTryDiscardButton.contentDescription =

Now, when the view is thumbs-down, the content description is “Discard”.

To do the same for the opposite state, add this to the top of hideEditableFields():

recipeDetailTryDiscardButton.contentDescription =

Great! Now, when the button changes state, the content description changes with it.

Run lint now. You’ll see fewer ContentDescription warnings — there should be three left. You’ll handle these slightly differently from the rest.

Ignoring Views

When you design for accessibility, you need to think about two types of content:

  • The content you create, which you can describe.
  • The content the user uploads, which you can’t possibly know how to describe — unless you have supernatural abilities and can predict what future users will upload.

But seriously, how do you support accessibility for user-generated content? For example, in the app, each recipe has a different image. Eventually, these images will come from users, so you need to consider the problem now.

There are two standard solutions for this situation:

  1. Ask the user to describe the image. Then you can include that description in the API response and set it to contentDescription.
  2. Tell the accessibility services that this view isn’t important for it to read so it will skip the view.

The simplest answer is the second solution.

You already learned how to set a String resource as contentDescription, which you should do for the content you provide. This time, you’ll tell the system that it shouldn’t read the view by setting contentDescription to @null.

In fragment_discover.xml, find the ImageView with the ID discover_recipe_image. This is the image that comes with the recipe.

Add @null as the contentDescription for the view with ID discover_recipe_image:


Perfect. Now the screen reader has the information it needs about this image.

Note: In Chapter 4, “Perceivable — Layout & Labeling”, you’ll learn about importantForAccessibility, which sends a similar message but has slightly different behavior.

Now, run the linter. Oh, look! There’s another case where it feels uncertain about the content description text: decorative views.

Two more linter warnings.
Two more linter warnings.

Handling Decorative Views

Go to fragment_recipe_detail.xml and find a view with the ID recipe_detail_divider. As the name suggests, this view is a thin line that acts as a divider between sections. What should the user know about this?

Because it’s a decorative view, it doesn’t add any meaning. You can safely ignore decorations using android:contentDescription="@null". This guideline applies to elements like dividers or gradients.

You could add that here. However, you have another option!

This doesn’t need to be an ImageView. Instead, you’ll set it as a plain old View with the color as the background. Change ImageView to View:


And replace android:src with android:background:


The final view should look like this:

  android:background="?colorControlHighlight" />

Notice that the linter warning doesn’t appear in Android Studio anymore. That’s because accessibility services skip these View elements unless you signal that a view is important for accessibility.

Throughout this book, you’ll learn some ways to tell the accessibility services how to treat a plain view. Content description is only one of them.

Run the linter again. There’s one ContentDescription warning left. You’ll have the opportunity to fix it in the challenge at the end of this chapter.

Only one linter warning.
Only one linter warning.

Jetpack Compose Tip: Composables such as Icon and Image have a contentDescription parameter to remind you to include a description. In cases where this isn’t available, you can use the semantics modifier: Modifier.semantics { contentDescription = "Edit" }.

But wait! You need to learn how to handle another common warning before you can call this chapter complete.

Fixing Keyboard Inaccessible Widgets

When you run the linter, you’ll see KeyboardInaccessibleWidget amongst the warnings. Find and click it to go to the detailed list of files.

KeyboardInaccessibleWidget warnings.
KeyboardInaccessibleWidget warnings.

This warning happens when you declare a view as clickable but not focusable. When you do this, you make the view interactive for people who tap it to perform the action, but not for someone who’s using a keyboard or similar technology to access it.

Often with keyboards, you first need to focus the view, then select it. If it’s not focusable, the user can’t perform the action. Again, this is something you can fix with only one line of code!

Open fragment_discover.xml, where all these warnings live. Find the ImageView with the ID discover_button_discard. Add the following property to the view:


And that’s all it takes! By adding the focusable property, you inform the system to let keyboard users focus on it.

While you’re at it, add the same property to the other two views in this file that need it. These views have the IDs discover_button_try and discovery_card_detail_button.


Run the linter. You’ve solved so many accessibility warnings! There should be much fewer. You’ll continue solving these throughout the book.

Build and run. It should behave the same as before when you’re not using accessibility services. In the next chapter, you’ll learn how to use accessibility services so you can experience the impact of your changes.

Now that you’ve gotten your hands dirty, and are hopefully ready to focus on some theory, this chapter will pivot to bring you up to speed on accessibility guidelines.

Defining WCAG

This book teaches you the best practices for and conforms to the Web Content Accessibility Guidelines (or WCAG) version 2.1 as the standard for accessibility. These guidelines are an industry standard.

WCAG is exactly as the name suggests: a guideline for making your web content or app accessible. The guidelines help you build your app so it’s consumable to a wide range of people. It doesn’t address every user’s needs — nor does this book — but it covers the main points and best practices.

Even though the guideline’s title suggests they are for the “web”, they’re useful for Android engineers to follow, and here’s why:

  • Used for legal requirements: Remember the legal requirements you learned about in Chapter 1, “Why Accessibility”? Many legal battles use WCAG as the guidelines for how to comply with accessibility laws across countries. That alone should make it a priority for you to learn.
  • Common language with others: You can use these guidelines across platforms. This means that you can talk about accessibility using this language with iOS and web engineers, as well as customers, product managers and designers.
  • Written with mobile in mind: As of version 2.1, WCAG includes requirements that apply to websites, mobile apps and tablets.

Note: As an example, the content descriptions you added help you satisfy the first requirement:

Guideline 1.1 Text Alternatives: Provide text alternatives for any non-text content so that it can be changed into other forms people need, such as large print, braille, speech, symbols or simpler language.

You’ll dig even deeper into this guideline in Chapter 4, “Perceivable — Layout & Labeling”.

Building a POUR App

WCAG uses a lovely little acronym to categorize the principles of the requirements:

  • P erceivable
  • O perable
  • U nderstandable
  • R obust

Perceivable, Operable, Understandable, Robust.
Perceivable, Operable, Understandable, Robust.

This book is organized by these principles, and you’ll learn more about each of them as you go. Let them serve as a mental checklist: “Is my app perceivable, operable, understandable and robust?”

Content descriptions fall under “perceivable”. They allow your user to perceive what’s in an image or understand a button’s purpose.

Making a view both clickable and focusable falls into the “operable” category. It lets people using a keyboard operate your app.

Measuring Using Levels of Compliance

WCAG provides levels of conformance for each of its guidelines, which include A, AA, and AAA, going from lowest to highest. Level A conformance means you’ll meet most people’s needs. The industry best practice is to meet at least A or AA.

Guideline 1.1 Text Alternatives, which covers content descriptions, is level A. It’s one of the first guidelines, and all apps should conform to it.

Note: This book will reference WCAG and the Android resources throughout. If you’re curious enough to look into these, head to WCAG at and the Android docs resource at

Choosing Universal Design

There are two predominant strategies for designing accessible apps: accessible design and universal design.

  • Accessible design is self-descriptive: It’s design that makes your app accessible. Features could be accessible in and of themselves, or you could create a tailored experience so someone using assistive technologies gets a different version of the app.

  • Universal design is a subset of accessible design, with the intent being to ensure that everyone gets the same experience. The design is accessible by default, so you don’t need to create a second design specifically for those using assistive technologies.

“The intent of universal design is to simplify life for everyone by making products, communications and the built environment more usable by as many people as possible at little or no extra cost. Universal design benefits people of all ages and abilities.”

-The Center for Universal Design at NC State University College of Design

This book uses universal design wherever possible because it makes your user experience consistent for all users, and it protects user privacy. With universal design, you don’t need to pry into your users’ data to see if they use assistive technologies.

At this early stage in the book, you’re already better equipped to start improving accessibility in any app! Before you move on to the next chapter, here’s an opportunity to practice what you’ve learned already.


Challenge 1: Solve the ContentDescription warning

Remember, the last time you ran the linter there was one more ContentDescription warning. Now’s the time to show your stuff by solving it!

In item_onboarding.xml you’ll see an ImageView with the ID onboarding_image. This is the view with the linter warning. If you remember, the on-boarding screen has an image with each slide. How do you think these should behave?

For this challenge, solve the ContentDescription warning on the on-boarding screen. You’ll need to decide whether to ignore it or add a description. If you get stuck, review the solution in the challenge project in the chapter materials.

Some tips and hints:

  • To see the on-boarding screen again when you run the app, check Show on-boarding in the Settings screen. Then, next time you close and open the app you’ll see the on-boarding screen.
  • If you decide to set the content description programmatically, set this view’s properties in OnboardingPagerAdapter in OnboardingActivity.kt.
  • Again, if you set the content description programmatically, make the warning go away in the XML file by either adding a placeholder content description or by suppressing the lint using tools:ignore="ContentDescription". If you suppress the warning like this, it’s helpful to add a comment to clarify that it’s set programmatically.

Key Points

  • Android lint is a useful tool for identifying accessibility issues.
  • One or two lines of code can often solve accessibility warnings.
  • Content descriptions tell accessibility services what a given image is.
  • To improve keyboard users’ experiences, add android:focusable="true" to a view that you declare android:clickable="true".
  • WCAG and the Android docs are excellent resources for understanding accessibility requirements.
  • Choose universal design over accessible design so that everyone has the same experience in your app.

Where to Go From Here?

Congrats! You’re already making great improvements to the app. If you got stuck at any point, check out the final and challenge projects included in the chapter materials.

Think about the huge impact you’ve already made on this app. Before your changes, there was no straightforward way for a person using a screen reader to know how to save a recipe, and there would be no way for someone using a keyboard to dismiss a recipe to move to the next.

Before you can get much further with your new knowledge, you’ll need to learn more about the common assistive technologies people use and about testing technologies that can detect potential issues. Move on to the next chapter to fill in those gaps and move one step closer to being an accessibility detective!

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.
© 2024 Kodeco Inc.