Distrito Telefónica. Innovation & Talent Hub

Back
Development

Improve your Android accessibility with toggleables

Customizing the views in our Apps is something very common in our daily development of screens but we often forget to review the accessibility part, so we end up creating beautiful visual components but not very accessible for people with disabilities. In this post we are going to see how we can improve accessibility in a personalized way for our Android apps working with toggleables Views, so that you can understand and apply any accessibility customization according to the needs of your App.

Before starting
The accessibility tests in this article use the most common accessibility service on Android: Talkback. Nevertheless, the code implemented here is standard for any other accessibility service that requires it.

Let’s get started
Let’s say we have an application that displays a Switch-type configuration but with an advanced layout where it has a title, a subtitle, and an image associated with it.  
Following is the code of this custom component. Note that inside the ConstraintLayout you can add as many customizations as you want to your toggleable component.
<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/custom_toggleable_root_layout"
    ... >
    <ImageView
        android:id="@+id/custom_toggleable_left_image"
        ... />
    <TextView
        android:id="@+id/custom_toggleable_title"
        ... />
    <TextView
        android:id="@+id/custom_toggleable_subtitle"
        ... />
    <androidx.appcompat.widget.SwitchCompat
        android:id="@+id/custom_toggleable_switch"
        ... />
</androidx.constraintlayout.widget.ConstraintLayout>
From here, let’s continue with this simple example to focus on what’s important: how accessible is this component? How can accessibility be improved? Here’s a video of Talkback’s default behavior with the component:
As seen in the video, Talkback’s focus moves independently between the component’s elements. However, if we look at other apps that follow the WCAG accessibility standard, such as the Android Settings app, we see the following:  
In this case, the focus is managed as a group for the entire component. This offers us two main advantages:
· More efficient communication with the user by announcing all the elements at once. 
· Easy Switch turn-on and turn-off with a single group focus.

Hands on
Now that we have seen how the accessibility can be improved for custom toggleables, let’s understand the steps to achieve it. We are going to see how to configure the accessibility of the initial component to offer the best experience to our users.

Disable the current accessibility configuration
First, disable the accessibility configuration of our child views by setting importantForAccessibility=”no”. It will be managed as a group in the root ConstraintLayout.

<ImageView
        android:id="@+id/custom_toggleable_image"
        android:importantForAccessibility="no"
        ... />
<TextView
        android:id="@+id/custom_toggleable_title"
        android:importantForAccessibility="no"
        ... />
<TextView
        android:id="@+id/custom_toggleable_subtitle"
        android:importantForAccessibility="no"
        ... />
<androidx.appcompat.widget.SwitchCompat
        android:id="@+id/custom_toggleable_switch"
        android:clickable="false"
        android:importantForAccessibility="no"
        ... />

Note that clicking is also disabled for Switch, as we do not want Talkback to focus on the Switch at all. We will see later why this is necessary.

Customize the root layout accessibility
Since we are going to apply a custom accessibility configuration, we need to create a custom Switch component class.
class CustomSwitchComponent @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : ConstraintLayout(context, attrs, defStyle) {
    // Custom accessibility configuration will go here
}

Now we can change the generic ConstraintLayout in the XML file to the custom one we just created.
<com.your.package.CustomSwitchComponent
        android:id="@+id/custom_toggleable_root_layout"
        ... >
<!-- Child views -->
</com.your.package.CustomSwitchComponent>
Add a content description
After creating the custom component, the first thing we are going to do is create a content description since the accessibility of the child views have been disabled with importantForAccessibility=”no”.

class CustomSwitchComponent(...) {
private lateinit var titleView: TextView
    private lateinit var subtitleView: TextView
    private lateinit var switchView: SwitchCompat
    override fun onFinishInflate() {
        super.onFinishInflate()
        titleView = findViewById(R.id.custom_toggleable_title)
        subtitleView = findViewById(R.id.custom_toggleable_subtitle)
        switchView = findViewById(R.id.custom_toggleable_switch)
        initAccessibilityConfiguration()
    }
    
    private fun initAccessibilityConfiguration() {
        contentDescription = "${titleView.text}. ${subtitleView.text}"
    }
}

Identify the component as a Switch
The next step is to identify our custom component as a Switch so that it is correctly announced by Talkback.

To do this, we need to change the className attribute in the AccessibilityEvent and AccessibilityNodeInfo classes when they are initialized.
class CustomSwitchComponent(...) {
    ...

    override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) {
        super.onInitializeAccessibilityEvent(event)
        event?.className = android.widget.Switch::class.java.name
    }

    override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
        super.onInitializeAccessibilityNodeInfo(info)
        info?.className = android.widget.Switch::class.java.name
    }
}


Provide the Switch state to the component
To provide the state of the Switch to our component, there is the setStateDescription function that we can use in the following way:
private fun initAccessibilityConfiguration() {
    ...
    ViewCompat.setStateDescription(this@CustomSwitchComponent, ViewCompat.getStateDescription(switchView))
}

The previous code is fine, and we can check that Talkback does indeed announce the state of the Switch in our component.

But this does not end here… The state will not be automatically updated in our component when we change the Switch state! We need the component to manage the update of the Switch state in a controlled manner.

This is the reason for disabling the click on the Switch previously, as we want all state changes on the Switch to go through our component so we can update the state properly.

A simple way to achieve this is by creating public utility functions so that the state of the Switch can be changed at the same time as we update it in our component.

fun changeSwitchState(enable: Boolean) {
    switchView.isChecked = enable
    ViewCompat.setStateDescription(this@CustomSwitchComponent, ViewCompat.getStateDescription(switchView))
}

fun getSwitchState() = switchView.isChecked

With these methods, we can change the state of the Switch through our custom component.

val component: CustomSwitchComponent = findViewById(R.id.custom_toggleable_root_layout)
component.setOnClickListener {
    component.changeSwitchState(!component.getSwitchState())
}

You now have a custom toggleable component with great accessibility configuration. It looks pretty good, huh?

But that’s not enough… we’re going to further improve accessibility in our custom Switch component.
Improve the component click announcement

If we test the component with Talkback we can see that it announces “Double-tap to activate”.

This is not entirely wrong, but if we test Talkback with an isolated Switch, we will see that it announces “Double-tap to toggle”, which gives more context to the user than just “activate”.

Since Android does not support getting this value, there is no way to get the ACTION_CLICK value from the Switch component directly unlike with the stateDescription value.

So, to change this part of the announcement we do it in the following way:
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
    ...
    info?.addAction(
        AccessibilityNodeInfo.AccessibilityAction(
            AccessibilityNodeInfoCompat.ACTION_CLICK,
            "toggle" // Remember to use a stringRes reference instead of hardcoded text.
        )
    )
}

With this code we can even further specify the click announcement depending on the state of the switch.
info?.addAction(
    AccessibilityNodeInfo.AccessibilityAction(
        AccessibilityNodeInfoCompat.ACTION_CLICK,
        if (switchView.isChecked) "turn off" else "turn on"
    )
)
So now, Talkback will announce one or another text depending on the state of the switch: · Switch is ON → “Double-tap to turn off”
· Switch is OFF → “Double-tap to turn on”

It’s a wrap
Congratulations! You have created a custom Switch component with excellent accessibility configuration that will make your App more accessible to users. Let’s see how our component looks like with all these accessibility improvements.
You can check the complete code of this blog in our open source repository: Accessibility Catalog.

In addition, this component has been added to Mistica Android, the open source component library that we have at Telefonica. You can get more information about the implementation in Mistica in the Lists section.

Any suggestions or improvements are welcome. Your valuable contributions helps create more accessible Apps for people around the world.

Thanks for reading!