Best practices to build accessible apps with Jetpack Compose
Tags: android, a11y, kotlin
Published: 2021-08-02 07:10:59

Hello! 
Here you are! You have finally decided to take a look at what it takes to make your android application accessible for people with visual impairment. First, that's great news ! You'll see that it's a rewarding path that you are about to take. 

Today I'm going to share with you a list of best practices to make your app accessible specifically for people who are blind. I will show you how to do that with Jetpack Compose. 

## How does Talkback work ?

Talkback is the screen reader on android used by blind or low vision users. 

Here are several gestures that you can perform anywhere on the screen, that allows you to navigate through each elements:

➡️: swipe to the right to go to next element

⬅️: swipe to the left to go to previous element


↕️: swipe up and down on the screen or swipe down and up, will open a menu. In this menu you can configure swipe up / swipe down gesture, for example: Navigation between headers, speech rate, read letter by letter (useful to read a password for example).

⬇️ or ⬆️: swipe up or down allows you to perform an action that you configured with the previous gesture.

👆: single tap on an element on the screen will give the focus to this element.

👆👆: double tap to activate an element for example a button.

When an element is focused, Talkback will give user informations about it. For example: 

1. its content
2. its value: for example for an Edit text.
3. Its type: "Button" "Edit box", "Tab", "List: 9 of 11 items".
4. Its state: "Selected" for a tab for example.
5. The action you can perform with it: "activate", "toggle", etc...

## Best practices list

### 1. Add content description to images

Let's take for example a shopping app. In the following screen, we have an image showing a black t-shirt. If we do nothing, blind users would not know what this element is unless they perform 8 swipes to the right, to reach its label "T-shirt with cut-outs tied". So we should add a description to this image. (and all the other images on this screen)

![Screenshot of a sample shopping application. Showing a black tee shirt but also buttons to pick another color. Then its price and a button Add to cart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6b341az27ow1ybrjc4z0.png)

Here is how to do that with Jetpack Compose.

```kotlin
Image( painter = rememberCoilPainter(product.url),
       contentDescription = product.contentDescription)
```

The good news with Jetpack Compose is that contentDescription is mandatory!

### 2. Hide decorative images

When an element is purely decorative and doesn't bring any additional information to the user (for example a background, an image to illustrate a text that doesn't add any meaningful information to it), you can hide it so that Talkback will not give the focus on this element.

```kotlin
Image( painter = painterResource(R.drawable.background),
       contentDescription = null)
```
### 3. Improve element’s description

In some situations the way that Talkback will read the element content will not be user friendly. Specifically in situations when we don't say something out-loud the same way that it's written. 

For example: the daily rate for a car rental: ***$16/day***

We will never say out loud: *"Dollar sixteen slash day"*, but if we do nothing in our app, Talkback will. That's not a very nice user experience.

By overriding the text attribute of our Text composable using the Modifier.semantics property, we are able to improve the information read by Talkback.

```kotlin
@Composable
fun DailyRate(rate: Int){
   Text(text = “$rate/day”,
        modifier = Modifier.semantics {
            text = AnnotatedString(“$rate dollars per day”)
        }
   )
}
```

### 4. Group elements together

In some situations, it can really improve the user experience to group element together. Imagine a screen with lots of elements on it. Imagine now that you have to go through every single element by swiping right. It's exhausting.


![Sample of a mobile app showing the profile of a pet sitter](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rltx40c5ho0dzmuyw8vj.png)

For example on this pet sitter profile screen. We can group together information like "no pets, home, car owner" to be focused and read all together. We can perform that by setting `mergeDescendants` to `true` in the `Modifier.semantics` properties of the main row.

```kotlin
Row (modifier = Modifier.semantics(mergeDescendants = true){}){
   Column {
       Icon(painter = painterResource(R.drawable.ic_pets),
           contentDescription = null)
       Text(text = stringResource(R.string.no_pet))
   }
   Column {
       Icon(painter = painterResource(R.drawable.ic_car),
           contentDescription = null)
       Text(text = stringResource(R.string.car_owner))
   }
   Column(/*...*/)
}
```

### 5. Change reading order

Sometimes, it makes sense to influence the reading order of the element on the screen to improve the user experience and sometimes avoid having a feature completely inaccessible because the element is unreachable.
For example every time you design something on the z-index, let's say with a floating action button like the "Compose" button in gmail. If you do nothing, the focus to this button will be given after going through all the elements of the emails list behind (if the list is not infinite...).


![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cowvyejw3vi05ghk6qtw.png)
 

There is no way to fix that with Jetpack Compose yet 😔... Here is an issue about that: [A11y - Need a way to change manually focus order](https://issuetracker.google.com/issues/186443263). Please comment or star the issue if you also think that it's important for the users.

To fix that without Jetpack Compose, you can use `android:accessibilityTraversalBefore` and `android:accessibilityTraversalAfter` properties to specify the element before and after the `FloatingActionButton`. 
```xml
<com.google.android.material.floatingactionbutton.FloatingActionButton
   android:id="@+id/fabButton"  
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:src="@drawable/ic_pencil"
   android:contentDescription="@string/create_new_email"
   android:layout_gravity="bottom|right"
   android:accessibilityTraversalBefore="@id/mailList"
   android:accessibilityTraversalAfter="@id/profilePicture"
   />
```
 
### 6. Facilitate navigation with headings

On the web, screen reader users are used to navigate quickly between headings (`<h1>`, `<h2>`, `<h3>`...). It gives them a quick overview of the content and help them to be more efficient.
Starting from Talkback 9.1, it's now also possible to do that in android apps, as long as the developer specifies it.

A good example to set up headings is when you have sections in a list. Like here in this settings screen: 

![Screenshot of the android settings to manage sound and vibration of the device. This screen has a list with different sections like Volume, Do not Disturb, Earphone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjox6yu75dmu00tl42y6.png)
 
To specify that sections of that list are headings, we have to add `heading()` inside the `Modifier.semantics` property.

```kotlin
@Composable
private fun Section(text: String) {
     Text(text = text,
       modifier = Modifier.semantics { heading() }
   )
}
```
### 7. Disable element and describe element state

Last situation I want to illustrate is the following: you have a list of items, and a switch button on each row of the list. 

![screenshot of an app. Setting's screen. List of setting, with on each row a text and a switch button at the end ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n92cbo0vs0tyuet0zgfd.png)
 
If we do nothing Talkback will behave on each row like this: 
1. Focus the entire row and say "Profile button"
2. Focus on the switch button: "On - Switch - Double tap to toggle"

This can be exhausting to go through a long list of configurable rows if Talkback stops twice per row. 

The original composable will probably looks like this: 

```kotlin
var checked by remember { mutableStateOf(true) }

Row{
   Text(text = "Profile details")
   Switch(checked = checked,
          onCheckedChange = { checked = !checked }
   )
}
```

A better user experience, could be something like this on each row : 
1. Focus the entire row and say "Profile button - ON - Double tap to toggle"

We will do that in two step : 
1. Disable the focus on the Switch button
2. Add toggle behaviour to the entire row

Let's go.

1) Disable the focus on the Switch button

We can do that by adding the `Modifier.clearAndSetSemantics` with nothing inside. This way talkback will ignore it. 

```kotlin
var checked by remember { mutableStateOf(true) }

Row{
   Text(text = "Profile details")
   Switch(checked = checked,
          onCheckedChange = { checked = !checked },
          modifier = Modifier.clearAndSetSemantics {  }

   )
}
```

Although using `clearAndSetSemantics{}` is the usual way to ask Talkback to ignore an element, keep in mind that in general when semantics properties can be changed by setting another default parameter of a composable, that should be preferred over using the lower level semantics modifier. In this situation, we can for example set `null` to `onCheckChange` callback, and the switch button will be ignored by Talkback too.


```kotlin
var checked by remember { mutableStateOf(true) }

Row{
   Text(text = "Profile details")
   Switch(checked = checked,
          onCheckedChange = null
   )
}
```

2) Add toggle behaviour to the entire row

We add a `toggleable` Modifier to the row and perform in it the same action than on the switch.
We add in the `semantics` Modifier the `stateDescription` properties to configure what Talkback should say regarding the switch state.

```kotlin
var checked by remember { mutableStateOf(true) }

Row(modifier = Modifier
        .toggleable(checked) { checked = !checked }
        .semantics {  stateDescription = 
                       if(checked) "ON" else "OFF" }
){
   Text(text = "Profile details")
   Switch(checked = checked,
                   onCheckedChange = null

   )
}
```

## That's it

That's it for my list of best practices to make your app usable for blind users.
You can also check the Compose documentation for accessibility here : [Jetpack Compose - Accessibility Documentation](https://developer.android.com/jetpack/compose/accessibility)

Of course accessibility is not only about blind people, so I really encourage you to learn more about that. Here is where you can start : [Mozilla website - what is accessibility.](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility)

I hope you enjoyed this article. Don't hesitate to reach out to me on Twitter if you have any questions : [@FannyDemey](https://twitter.com/FannyDemey)