Accessibility in prices, our learning from the Android perspective

How we improved the accessibility for our price component, and how it showed us the way towards new knowledge for our design system.

Juan Ignacio Unzurrunzaga
Mercado Libre Tech

--

Read this story in spanish.

Intro

This post springs from a need to start working on digital accessibility in order to improve our users’ experience. As Guille Paz tells us in his story about our work in digital accessibility, this initiative arises from the analysis carried out by our new accessibility team — a multidisciplinary team made up of people from UX and development — in an effort to break down the barriers that prevent the use of Mercado Libre products across our different applications.

As a continuation of the story made by Martín Di Luzio about learning how to develop price accessibility on the web, we believe it is appropriate to share the experience we’ve gained from the native development perspective, and to provide context to some important notions about accessibility at the Android development level.

What we have detected

As part of the first set of tasks we carried out at the beginning of the journey to improve the accessibility of our products, there comes about the Price component. We believe that this component is an exemplary case to share how we work in our analysis and subsequent implementation, since it represents a transversal element to many of the experiences of Mercado Libre, both due to its longevity and importance as well as to the large number of uses, among many of our business verticals.

Among the problems that we have found in the different implementations of Price, we can mention the following:

  • Inconsistency in reading currencies. As an example, for a price with the sign “$” representing “pesos”, the reader interprets and announces the word “dollars”.
  • Lack of a general context that allows recognizing the different prices that make up the “combo” variant as a single compound element (previous price + current price + discount). This results in unoptimized component navigation (navigation of three “loose” elements as opposed to one with a single overview).
  • Unsupported visual feedback with an alternative for the blind. In the case of the crossed-out price, which seeks to be interpreted as a previous price, we need the advertisement read to reflect this same state.
  • Reading of decimal and thousands separators. In this case, we detect that according to different configurations, the reader can interpret a number in the wrong way by taking the “thousand” separators as decimal separators. As an example, a price of “ten thousand” (10,000) can be read as “ten point zero”, or alternatively, “ten point zero, zero, zero”.
Example video with the previous implementation of the pricing component, and the resulting experience when navigating it with Talkback

Based on the feedback from our accessibility team, together with the participation of referents in the field and users of screen readers, we have reached the validation of the experience we wish to transmit when browsing prices.

As a solution to the issue raised, we decided to create a component that unifies the experience mentioned above, and that maintains visual consistency and the standards with which we manage our UI components throughout our ecosystem. This component we decided to call MoneyAmount became part of Andes UI, our design system. It has three pillars that structure the work of all our teams: Design (Figma), Web (React) and Native (Android and iOS).

On the basis of accessibility in Android

The accessibility model according to Google

As a small introduction to the development work related to accessibility in our components, we find it necessary to explain the workings of what Google calls the “accessibility model”. For people who are not users of accessibility services, the way to interact with an app is direct: the UI presents information, and the person interacts directly with this UI.

Interaction between non-users of accessibility services and the UI. The UI presents information and the person acts on it.
Interaction between non-users of accessibility services and the UI.
The UI presents information and the person acts on it.

Instead, for people who use accessibility services, the interaction model changes: the person interacts with the service, and the latter is the intermediary with the app.

The main reason for this is clear: since the services are external agents to each app, this model allows the developer to work in an agnostic way and leave up to the framework the responsibility of interpreting the relevant information for each service to work correctly.

Interaction between users and different accessibility services (Talkback, Switch Access, Braille Keyboard, among others). These services interact with the framework, which provides the necessary information for each of them to function correctly.
Interaction between users and different accessibility services (Talkback, Switch Access, Braille Keyboard, among others). These services interact with the framework, which provides the necessary information for each of them to function correctly.

To learn more about the subject, we recommend watching this video.

From the UI to the Android Framework: the accessibility tree

Taking this accessibility model into account, we see that all the information the framework needs for the services to work correctly is condensed in the accessibility tree. The accessibility tree is a representation of the hierarchy of views that a screen has, where each View (or tree node) is represented by an AccessibilityNodeInfo object. As an example, in the following graphs we show a tree of views and the accessibility tree that emerges from the first (it is to be noted that all data is fictitious and is shown for reference).

Example of a tree of views that represents a UI. Inside, we have several viewGroups (layouts) that contain other views (imageViews to display images, textViews for plain text, buttons, checkboxes).
Example of a tree of views that represents a UI. Inside, we have several viewGroups (layouts) that contain other views (imageViews to display images, textViews for plain text, buttons, checkboxes).
Equivalent accessibility tree, where each view is represented by a node that contains all the information needed by the different accessibility services. Some of the properties are added as an example.
Equivalent accessibility tree, where each view is represented by a node that contains all the information needed by the different accessibility services. Some of the properties are added as an example.

Within the AccessibilityNodeInfo, all the necessary information is concentrated so that each service can know the structure and various attributes of our view (measures of width, height, states, sequential order of navigation, supported events such as clicks, long-clicks, gestures scrolling, etc.). What’s paramount about this is to understand that the information that the framework provides to each accessibility service is contained in this object, and that there is no direct interaction between the views of the app and the corresponding service.

Screen readers on Android and on the Web: a key fact

Having somewhat explored the backstage of accessibility and the android framework, we share an important insight we have come across during the research stage. On drawing an analysis of the difficulties found on the Web in terms of the diversity of screen readers and browsers (again I refer you to the interesting and very complete story written by Martín Di Luzio) and the existence of a single reader on Android — the Talkback, we find a significant advantage: on Android, we don’t find the inconsistencies that do occur on the Web when reading numbers, decimal separators and texts with different styles such as strikethrough texts or superscripts. This ensures that the reading of the accessibility tree by the screen reader will always be consistent, and that the solution that is developed will be uniform and compliant with the standards that users expect when interacting with a robust application.

About development (Take me to the code!)

How we handle accessibility in our components

Before delving into the MoneyAmount implementation, it’s worth including a note about how we typically handle accessibility in the vast majority of our components. Since these are usually custom views, we need to take care of several simultaneous features that make for a good user experience. These aspects include the correct reading of the description and role of the component, the correct exposure of the available states (enabled/disabled, error, activated/deactivated, etc), the available actions that can be performed on it (click, long-click, etc), the warning before events on the component (load warnings when a button goes to a “loading” state), among others.

All Android visual components, native or custom, must inherit directly or indirectly from the View class, and therefore have an inherited delegate of the type View.AccessibilityDelegate, which is the object that will handle all the interaction between the component and the accessibility service. In addition, we are offered a setter which, if necessary, allows us to add a custom delegate.

Diagram that represents the class inheritance of our components, explaining the inheritance of View and the dependency with the accessibility delegate. Within the delegate, the methods that will configure the nodeInfo are shown as an example (generate text, handle state, generate available actions)
Diagram that represents the class inheritance of our components, explaining the inheritance of View and the dependency with the accessibility delegate. Within the delegate, the methods that will configure the nodeInfo are shown as an example (generate text, handle state, generate available actions)

To keep all the logic for handling accessibility issues more encapsulated (respecting the principle of Separation of Concerns and therefore improving the cohesion, readability and testability of the code), for each component we create a custom delegate that inherits from View.AccessibilityDelegate, and that will have access to the information necessary to apply the logic corresponding to each aspect to be configured. The common basic structure of our delegates is as follows:

This custom delegate is set in the main class once instantiated.

Within this custom delegate we have access to the AccessibilityNodeInfo, the object on which we will edit the necessary information according to the need of the component.

Working on this AccessibilityNodeInfo, we are able to add a contentDescription (the alternative text, which many will know from the web platform); say if the component isEnabled or isChecked, we can add actions such as “expand”, “close”, or any other custom action we decide to add depending on the component, among a large number of other editable parameters.

MoneyAmount

The component that represents the most atomic version of price is MoneyAmount. This component has three variants: POSITIVE, NEGATIVE, and PREVIOUS.

Component examples with type = MoneyAmountType.POSITIVE
Component examples with type = MoneyAmountType.POSITIVE
Component examples with type = MoneyAmountType.NEGATIVE
Component examples with type = MoneyAmountType.NEGATIVE
Component examples with type = MoneyAmountType.PREVIOUS
Component examples with type = MoneyAmountType.PREVIOUS

When we begin to analyze the Talkback experience with this component, we find that the system infers the type of currency and the description of the decimals according to external values ​​within our reach, and that they have to do with the configuration of Talkback, both of the synthesizer of voice as well as of the language, and even the language in the device. In addition, the system does not make out the difference between crossed-out and non-crossed out prices at the time of reading.

Further, we have to consider the possibility of handling more than one currency in the same context, and that the responsibility of defining the correct description text be on the side of our component structure, and not of the implementation in the frontend. To handle these variables, there is the need to encapsulate this responsibility in a class that will contain this information regardless of the Talkback and device values:

Likewise, in order to correctly handle the amount and thus convert it into readable text, we need to know the characters used as thousands and decimal separators, values ​​that change depending on the country of each currency. Thus, we see the need to create yet another helper class:

Now, with these classes, the logic for generating the contentDescription is configured as follows:

As a note of color, it’s important to keep in mind that the description generation method is nested in a companion object, which allows it to be accessed statically. This will be necessary for the creation of the combo component description, explained below.

MoneyAmountDiscount

This component corresponds to the discount block.

MoneyAmountDiscount Example
MoneyAmountDiscount Example

Just as is done with MoneyAmount, to manage the description of this component we generate a custom delegate that creates a string through a resource with different translation options for Spanish, Portuguese and English.

Examples of the resulting string in different languages:

-“9 por ciento de descuento.”

-“9 percent off.”

Like the previous component, the description generation method is added as static, so it can be reused in the combo component.

MoneyAmountCombo

This component corresponds to the price block that contains a previous value, a current value and the corresponding discount percentage.

MoneyAmountCombo example
MoneyAmountCombo example

Structurally, we take advantage of the fact that we have the components available separately, and we take this component as an internal price container.

For this case, we take the set as a semantic view. This means that since the elements that make up the set have a common meaning, it is better to present them as a single element whose description is the conjunction of the individual descriptions. In this way, we reduce the amount of noise and achieve a more direct and less frustrating navigation.

From the code, we group all the internal descriptions into a unique general description to assign to the component, and make it focusable. Thus, the system automatically takes care of “hiding” the internal nodes and passing the component as a single node with its own description. The only addition to the text resulting from joining the three internal descriptions is the addition of the text “now” at the current price, which is not part of the description of the individual MoneyAmount (in this case, the one that shows $1,350). The resulting description for this example looks like this:

-“Ahora: 1350 pesos, diez por ciento de descuento. Antes: 1500 pesos.”

-“Now: 1350 pesos, ten percent off. Before: 1500 pesos.”

The code of the main class with the accessibility settings looks like this:

The custom delegate takes advantage of the static description generation methods that are present in the custom delegates of the individual components:

Finally, we show a video with the definitive experience of the component:

Example video with the current implementation of the pricing component, and the resulting experience when navigating it with Talkback

Our lessons learned

At the level of development

It seems relevant to us to clarify once again that Google’s accessibility model allows us to work on the accessibility of our applications in an comprehensive way, without differentiating which specific service we are working towards, but creating a complete solution for all the tools that allow an experience that is perceptible, operable, understandable and robust for all users.

We have found that when developing the accessibility of a visual component, it is much easier to respect the separation of concerns by creating our own accessibility delegates with much of the solution code. Furthermore, working directly on the nodeInfo allows us to add properties without the need to override native methods such as setContentDescription. On the other hand, adding these properties in the last step before they become immutable prevents the overriding of these values ​​on the side of the frontend that implements the component.

At the level of experience in accessibility

Working together on accessibility issues is key. We need multiplicity of voices and even more so the presence of people who use assistive technologies to help us validate the expected behaviors.

Within this joint work, we find that in design systems, it is key to think of an accessible-first approach, i.e. to include the accessible behavior at the moment of the component’s conception. It is always better to embed it in the design stage than to add it as a late fix that removes the barriers that we ourselves create. In this way, we ensure that the experience provided will be 100% accessible from the word go, and that we will avoid rework in development, which will have a negative impact on lead times.

From a more personal perspective, we think the combo component is a good example of a semantic view. We can see that by applying this concept, we achieve a better description of the view while simplifying the navigation from three elements to one. Also, by having to define a single description for the entire element, we can choose to mention the value that is most visually prioritized given its size and text color first, followed by the least prioritized values, thereby achieving a more consistent experience across users with different degrees of vision.

Final comments

Training in accessibility is an ongoing development process. We know that there are no formulas or recipes that can, by themselves, break down accessibility barriers. That’s why we must be especially aware that generating, seeking and sharing knowledge through research and experimentation, working as a team and with a multiplicity of voices is something that should always be encouraged to strengthen digital accessibility as a key goal in the context we live in.

--

--