Blog Infos
Author
Published
Topics
,
Author
Published

Recently new codelab was launched for Android which covers the accessibility in Jetpack Compose.

In this article, we will learn how to implement accessibility in Jetpack Compose.

Major properties that help to improve the app’s accessibility

  • Touch target size
  • Click labels
  • Custom actions
  • Content description or Visual element descriptions
  • Headings
  • Custom merging
  • State descriptions
  • Custom navigation

 

  • You should make sure that any on-screen element that the user can click, touch, or interact with have a width and height of at least 48dp.
  • If these controls are sized dynamically, or resize based on the size of their content, consider using the sizeIn modifier to set a lower bound on their dimensions.
Text(
    text = "SizeIn sample",
    modifier = Modifier.sizeIn(
        minWidth = 48.dp,
        minHeight = 48.dp
    )
)
  • Some Material components set these sizes for you 🎉

Rather than doing it by ourselves, we can use IconButton composable that has a minimum touch target size of 48 x 48dp to meet the accessibility guidelines.

https://developer.android.com/codelabs/jetpack-compose-accessibility/img/6dbe0b3c0ee055e0.png?authuser=4

  • As you can see on the left close button’s size was 24 dp, and after fix it is 48 dp which is required.
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cd_show_fewer),
modifier = Modifier
.clickable { openDialog = true }
.padding(12.dp) // adding 12 dp padding helps us to meet the requirements of 48dp touch target size
.size(24.dp)
)
  • Clickable elements by default don’t provide any information on what clicking that element will do. Therefore, accessibility services like TalkBack will use a very generic default description.
  • We can provide a specific description that explains what will happen when the user clicks this element which helps to provide the best experience for users with accessibility needs.

https://developer.android.com/codelabs/jetpack-compose-accessibility/img/e4aea6991ba119c0.gif

 

 

  • As you can see that on the left side talkback just says Double-tap to activate , after improvement on the right side, it says Double-tap to read article.

You just need to provide the label do not add Double-tap to to your label, talkback will handle it for you based on the event (click, longClick)

Row(
Modifier.clickable(
onClickLabel = stringResource(R.string.post_card_hostiry_click_label) // read article
) { navigateToArticle(post.id) }
) {
// content
}
  • With the help of the custom accessibility actions, we can group together actions that relate to the same list item.
  • We can pass the list of CustomAccessibilityAction’s to customActions semantic property
Row(
Modifier
.clickable(
onClickLabel = stringResource(R.string.post_card_history_click_label)
) {
navigateToArticle(post.id)
}
.semantics {
customActions = listOf(
CustomAccessibilityAction(
label = "Show fewer like this"
// action returns boolean to indicate success
action = { openDialog = true; true }
)
)
}
) {
// ...
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
IconButton(
// it makes sure that Talkback will not interact with this item
modifier = Modifier.clearAndSetSemantics { },
onClick = { openDialog = true }
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cd_show_fewer)
)
}
}
}

On the left, you can see the default situation, where each cross icon is focusable. On the right, you can see the solution, where the action is included in the custom actions in TalkBack:

  • Accessibility services have no way to make sense of visual elements based on their pixels alone.
  • Visual composables like Image and Icon include a parameter contentDescription where we can pass a localized description of that visual element, or null if the element is purely decorative.
Image(
painter = painterResource(R.drawable.post_1),
contentDescription = null, // decorative
contentScale = ContentScale.Crop,
modifier = Modifier
.height(100.dp)
.fillMaxWidth()
)
Image(
painter = painterResource(R.drawable.post_1),
contentDescription = stringResource(R.string.content_desc_post_header)
contentScale = ContentScale.Crop,
modifier = Modifier
.height(100.dp)
.fillMaxWidth()
)
  • We can indicate which parts of the text are headings which helps users to navigate quickly through different headings by swiping up or down ⬆️ ⬇️.
  • We can set the heading semantics property to indicate that this composable is a heading.
Text(
text = post.title,
style = MaterialTheme.typography.h6,
modifier = Modifier.semantics {
heading()
}
)
view raw Heading.kt hosted with ❤ by GitHub

  • Accessibility services like TalkBack navigate a screen element by element.
  • By default, each low-level composable in Jetpack Compose that sets at least one semantics property receives focus.

For example, a Text composable set the text semantics property and thus receives focus.

  • Composables can be merged together using the semantics modifier with its mergeDescendants property.

The metadata of the article is currently read aloud as several separate items. It can be improved by merging that into one focusable entity:

Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
Image(
)
Spacer(Modifier.width(8.dp))
Column {
// content
}
}

  • By adding a custom description for the state of the composable we can improve the accessibility of composable components.
  • For example, Checkbox status is read as either “Ticked” or “Not ticked”. We can replace this description with a custom description by using the stateDescription property inside the semantics modifier:

https://developer.android.com/codelabs/jetpack-compose-accessibility/img/695e7fc091c77611.gif?authuser=4

 

private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
// ...
val stateNotSubscribed = stringResource(R.string.state_not_subscribed)
val stateSubscribed = stringResource(R.string.state_subscribed)
Row(
modifier = Modifier
.semantics {
// custom description based on the state
stateDescription = if (selected) {
stateSubscribed
} else {
stateNotSubscribed
}
}
.toggleable(
value = selected,
onValueChange = { _ -> onToggle() },
role = Role.Checkbox
)
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
// ...
Checkbox(
checked = selected,
onCheckedChange = null,
modifier = Modifier.align(Alignment.CenterVertically)
)
}
}

Job Offers

Job Offers


    API Engineer

    American Express
    London
    • Full Time
    apply now

    Android Engineer

    American Express
    Phoenix, USA
    • Full Time
    apply now

    Mobile Developer Android (m/w/d) Firebase

    REWE digital
    Köln / Cologne
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

Jobs

Focus Modifiers helps us to customize the navigation between different composables

We can interact with the focus mechanisms in Compose with the help of the following modifiers.

  • Modifier.focusTarget(): Allows making the component focusable
Text(
    text = "Hello World",
    modifier = Modifier.focusTarget()
)
  • Modifier.focusOrder(): In combination with FocusRequesters, this allows us to change the focus order
val (first, second) = FocusRequester.createRefs()
You can also pass lambda to customize the focus events behaviour
Text(
    text = "Hello World",
    modifier = Modifier
        .focusOrder(first) {
            this.down = second
        // down, left, right, up, previous, next, start, end
        }
)
  • Modifier.focusRequester(): By adding a custom FocusRequester it allows us to request focus for individual components
val (first, second) = FocusRequester.createRefs()
Text(
    text = "FocusRequester",
    modifier = Modifier
        .focusRequester(second)
)
// requestFocus 
second.requestFocus()
  • Modifier.onFocusEvent()Modifier.onFocusChanged():Allows us to observe the changes to focus state
Modifier.onFocusChanged { focusState ->
    with(focusState) {
        when {
            hasFocus -> {
                Log.d("A child of mine has focus!")
            }
            isFocused -> {
                Log.d("I'm focused!")
            }
        }
    }
}

 

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
blog
Yes! You heard it right. We’ll try to understand the complete OTP (one time…
READ MORE
blog
The dictionary defines the term side-effect as an undesirable effect. While this applies to…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu