Implementing TalkBack Support for Android Applications
TalkBack is Android's screen reader, the equivalent of iOS VoiceOver. Enabled via Settings → Accessibility → TalkBack or Volume Up + Volume Down long press on most devices. With TalkBack enabled, single tap focuses and announces the element, double tap activates it. Right/left swipe moves between elements.
What Breaks Without Proper Implementation
ContentDescription and ImportantForAccessibility
android:contentDescription — equivalent to accessibilityLabel on iOS. Mandatory for ImageView, ImageButton. For TextView, TalkBack reads text automatically. Decorative images: android:importantForAccessibility="no" (XML) or ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO) — TalkBack skips them.
Common mistake in Compose: Icon(painter = painterResource(R.drawable.ic_close), contentDescription = null) — icon is unreachable for TalkBack if not wrapped in a clickable element with explicit description. IconButton with contentDescription on Icon — correct.
Element Grouping
ViewGroup with android:focusable="true" and android:importantForAccessibility="yes" — TalkBack reads all child elements as one: container's contentDescription. For product card (image + name + price + button) — better to make card one accessible element with composite contentDescription via ViewCompat.setAccessibilityDelegate.
In Jetpack Compose: Modifier.semantics(mergeDescendants = true) { contentDescription = "Product: $name, price: $price" } — merges all children into one for TalkBack.
AccessibilityDelegate and Custom Actions
TalkBack announces standard actions by default: "Double-tap to activate". Custom actions (swipe card → delete, long press → menu) must be registered explicitly:
ViewCompat.setAccessibilityDelegate(cardView, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View, info: AccessibilityNodeInfoCompat
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.addAction(
AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfoCompat.ACTION_DISMISS,
"Remove from list"
)
)
}
override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
if (action == AccessibilityNodeInfoCompat.ACTION_DISMISS) {
removeItem()
return true
}
return super.performAccessibilityAction(host, action, args)
}
})
Live Regions for Dynamic Content
Cart counter, countdown timer, load status — content changes without user action. android:accessibilityLiveRegion="polite" — TalkBack announces the change when user is not busy. "assertive" — interrupts current announcement. In Compose: Modifier.semantics { liveRegion = LiveRegionMode.Polite }.
RecyclerView and Focus
TalkBack traverses RecyclerView linearly by elements. If RecyclerView is nested in ScrollView — focus can get stuck. RecyclerView should not be nested in ScrollView: standard Material Design recommendation and necessary for accessibility.
RecyclerView element with multiple clickable zones (e.g., card + "Add" button inside): ensure TalkBack focuses each zone separately, not just root view. descendantFocusability="blocksDescendants" on root breaks accessibility of child buttons.
Audit Process
Enable TalkBack on real device (not emulator — Samsung One UI and stock Android behave differently). Go through main user flows. Identify: elements without description, unreachable zones, incorrect focus order, missing live regions for dynamic data.
Tools: Accessibility Scanner (Google Play) — visually highlights problems directly over app. Android Studio Layout Inspector with Accessibility tab. Espresso with AccessibilityChecks for automated regression testing:
@Before
fun setUp() {
AccessibilityChecks.enable().setRunChecksFromRootView(true)
}
Timeframe: 3-5 days. On Samsung devices, need additional checking — One UI adds its own gestures on top of TalkBack, which can conflict with custom gesture recognizers.







