Accessible Custom Components

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

There are multiple things to consider when creating a custom component. The first is: should you even be making a custom component? You saw in the previous two lessons that you can get pretty far with just using existing components and modifiers. And you should! If you have the option to use something already existing provided in the Material or Compose Foundation libraries, use that so that all of your accessibility needs at the component construction level are handled for you.

As it is in this app, you’re already doing more than you need to (for learning sake). To drive home this point, simplify the favorite toggle feature.

Using Built-in Components

Back in DetailScreen.kt, look for the Icon with the toggleable modifier. Replace it with the following code:

IconToggleButton(
  checked = cat.isFavorite,
  onCheckedChange = { detailViewModel.toggleFavorite() }
) {
  Icon(
    imageVector = if (cat.isFavorite) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
    contentDescription = stringResource(id = R.string.detail_favorite_label)
  )
}

Using Canvas or Layout

Say you’re creating something really custom, such as a graph to display data. There are two main ways you can do this: by drawing directly onto a Canvas, or using a custom Layout. What option is more accessible? Time for an example to find out.

Text(
  text = stringResource(id = R.string.graph_title_box_layout),
  style = MaterialTheme.typography.h6,
  modifier = Modifier
    .padding(bottom = 8.dp)
    .semantics { heading() }
)
SleepBarGraph(
  naps = cat.naps, modifier = Modifier
    .fillMaxWidth()
    .height(32.dp)
)

Spacer(modifier = Modifier.height(16.dp))

Text(
  text = stringResource(id = R.string.graph_title_canvas),
  style = MaterialTheme.typography.h6,
  modifier = Modifier
    .padding(bottom = 8.dp)
    .semantics { heading() }
)
SleepChart(
  naps = cat.naps,
  modifier = Modifier
    .fillMaxWidth()
    .height(32.dp)
    .background(MaterialTheme.colors.onPrimary)
)
Detail Screen with both Graphs Displayed
Zoluex Vkbiud wukk tuzp Nneyjk Muyntayit

content = {
  naps.forEach { nap ->
    val formatter = DateTimeFormatter.ofPattern("h:mm a")
    val description = stringResource(
      id = R.string.nap_content_description,
      nap.start.format(formatter),
      nap.end.format(formatter)
    )
    Box(modifier = Modifier
      .background(MaterialTheme.colors.primary)
      .semantics {
        contentDescription = description
      })
  }
}
Graphs on Detail Screen with TalkBack reading a data point
Fsonfz un Gajeuf Rfqauk wetn TaykQinh zaavezp u tavi jiizw

Custom Actions

Speaking of adding actions to nodes, this lesson would not be complete without talking about custom actions. Custom actions, from an accessibility standpoint, can be used by TalkBack power users to accomplish tasks more easily.

.semantics {
  customActions = listOf(
    CustomAccessibilityAction(label = favoriteActionLabel, action = {
      onFavoriteClicked()
      true
    })
  )
}
val favoriteActionLabel = if (cat.isFavorite) {
  stringResource(id = R.string.action_label_unfavorite)
} else {
  stringResource(id = R.string.action_label_favorite)
}
TalkBack Context Menu
KechKury Rusbahs Qijo

TalkBack Action Menu with Favorite Action Listed
FusyPimm Uzviuy Kewi beld Dewewoyo Izgaiz Kobdec

See forum comments
Download course materials from Github
Previous: Introduction Next: Conclusion