Advanced Semantics

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

Semantics Tree

Before getting too deep into the code, it’s important to take a moment to understand how Jetpack Compose keeps track of all that lovely accessibility data you’ll be providing. This will help you understand not only how accessibility works in this world, but also how building an accessible app makes your app more testable.

View Tree saying "Draw Pencil" and Semantics Tree saying "Edit Action"
Ream Gcoe hugubz "Rwav Yecbad" uqh Mematfimv Sqou niqohg "Akul Olgeud"

Viewing the Semantics Tree

Most of the time, you won’t need to look at this tree, but some of us are plant enthusiasts—or have a habit of rescuing cats from trees—so you might as well take a look while you’re here.

Toggle Layout Inspector icon in Android Studio
Tozcto Yovoew Azztisnax iyiv ix Oyyqeur Pbokee

Layout Inspector with Attributes List
Mafaux Ulqyovbin qumh Olzfapidat Mukb

Testing Semantics

As hinted earlier, building for accessibility can also be helpful for testing. That’s because, for most Jetpack Compose UI test matchers and assertions, the test framework actually asserts against the semantics tree! Because of this, being able to view the semantics tree while running a test can be extremely helpful when diagnosing failures or building complex matchers.

@Test
fun detailScreen_verifyToggleAndHeading() {
  composeTestRule.setContent {
    CatNapperTheme { CatNapperApp() }
  }

  // 1. Navigate to the detail screen for the first cat
  composeTestRule.onNodeWithText("Luna")
    .performClick()

  // 2. Verify that the "Favorite" icon is toggleable
  composeTestRule
    .onNode(hasContentDescription("Favorite", substring = true, ignoreCase = true))
    .assertIsToggleable()

  // 3. Verify that the "Naps:" heading is displayed
  composeTestRule.onNode(isHeading() and hasText("Naps:"))
    .assertIsDisplayed()
}

Adding Heading Semantics

At the beginning of this lesson, we promised you’d learn additional semantics you can use. Now’s the time!

Text(
  text = stringResource(id = R.string.details_naps),
  style = MaterialTheme.typography.h6,
  modifier = Modifier.semantics { heading() }
)
Headings TalkBack Control
Juunalkr GejlFafs Quqsqac

Printing the Node Tree

Sometimes, you need a few extra hints about what’s happening in the semantics node tree when writing effective tests. Thankfully, there’s a handy way to do this.

composeTestRule.onRoot()
  .printToLog("TESTING123")
Logcat of Semantics Tree
Tomrut oj Fesesnirt Tkie

Collection Semantics

If you haven’t already, you’ll likely come across a time where you’re displaying a lot of information on a screen. Sometimes in a list, and sometimes in a more complicated arrangement.

TalkBack on a LazyColumn
MicdBond ex a CaznXorufr

Modifier.semantics {
  collectionInfo = CollectionInfo(cat.naps.size, 1)
}
Modifier.semantics {
  collectionItemInfo = CollectionItemInfo(
    rowIndex = index,
    rowSpan = 1,
    columnIndex = 0,
    columnSpan = 1
  )
}
Column(modifier = Modifier.semantics {
  collectionInfo = CollectionInfo(cat.naps.size, 1)
}) {
  val formatter = DateTimeFormatter.ofPattern("h:mm a")
  cat.naps.forEachIndexed { index, nap ->
    Text(
      text = "${nap.start.format(formatter)} - ${nap.end.format(formatter)}",
      modifier = Modifier.semantics {
        collectionItemInfo = CollectionItemInfo(
          rowIndex = index,
          rowSpan = 1,
          columnIndex = 0,
          columnSpan = 1
        )
      }
    )
  }
}
TalkBack Collection Info
ZukxGexw Xepqegkuun Oyde

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