Tools: Adding Navigation support to Large Content Viewer with Compose

Tools: Adding Navigation support to Large Content Viewer with Compose

Source: Dev.to

Keyboard Navigation Support ## Screen Reader Navigation Support ## Considerations for Voice Access ## Wrapping Up ## Links in the Blog Post In my previous blog post, Beyond Font Scaling: Large Content Viewer with Compose, I explained how to build a large content viewer from iOS using Jetpack Compose. The implementation did not include support for keyboard or other assistive tech navigation, so this blog post tackles those topics. A disclaimer: The way I'm presenting the support for assistive tech navigation in this blog post is one approach, and my goal is to provide examples and context, but as always, this is a demo project. In your production app, things might get a bit more complicated, and you might need to handle more variables. So always remember to test the solution, ideally with your users who use assistive technologies. And I hope it goes without saying: Before releasing to production. In this blog post, I’m presenting code for keyboard and screen reader navigation, then sharing some considerations for voice access support. The code relies heavily on the code presented in the previous blog post, so if you have questions about it, please check that post. A link for the full code is also provided at the end of the blog post. The pointer input implementation presented in the previous blog post relies on long-press, but keyboard navigation doesn’t support that kind of interaction, so we need another tactic to display the item preview. With a keyboard, focusing on an item is a natural choice, so we’re going to use that. The idea is that when a user who navigates with a keyboard or a keyboard-emulating device focuses on a bottom bar item for the same duration as a long press, we will show the item preview. We can do this with the following code: We use the onFocusChanged-modifier. Its state indicates whether the current element is focused via it.isFocused. If it is, we launch the coroutine scope, delay for the same amount of time as with long-press interaction, and then set previewedItem to the currently focused item. If the element is not focused, meaning the focus leaves the navigation bar item, we set the previewedItem to null. Now, when the user navigates with a keyboard and stays focused for the long-press time, we show the item preview: Alright, we now have keyboard navigation support. Let’s talk about screen reader navigation next. You might ask, “Why are we implementing something like this for screen reader users, as they can’t see?” The thing is, not every screen reader user is blind, and even blindness is a spectrum. So, some screen reader users might still benefit from seeing the icon preview. Okay, back to the code. For screen reader navigation, we’re going to go with custom accessibility actions. The code could look like this: We add a custom accessibility action via semantics and set its label to “Preview item”. In the action block, we set the previewed item as the current item. Then we return true from the action to indicate that it was successfully handled. You might ask, why don’t we use the long-press duration here? Well, if the user has already gone through all the trouble to trigger the accessibility action, they know what they’re doing and want to see the preview, so no point in making them wait. In touch and keyboard navigation, we don’t show it right away, as it might not be the desired behavior. After these changes, the navigation could look like the following. The video doesn’t have sound, but you can see the TalkBack input at the bottom of the screen. One assistive technology available to users is Voice Access, which allows users to navigate with voice commands. For users who use it with the item preview, we actually don’t need anything new - everything’s already in place. One of the commands available for Voice Access is to long-press an interactive element, and as this implementation already supports it (check the previous blog post), we don’t need to do anything to support it. In this blog post, we’ve continued with the icon preview, making it more accessible for assistive technology users. We first looked into improving keyboard navigation, then screen reader access, and finally Voice Access. There’s one consideration for the overall discoverability of the item previewer: it isn't a common pattern on Android, so you might want to consider how to let your users know about this cool new feature. The complete code is available as a Github Gist. Beyond Font Scaling: Large Content Viewer with Compose Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: NavigationBarItem( modifier = Modifier .onFocusChanged { if (it.isFocused) { scope.launch { delay( viewConfiguration.longPressTimeoutMillis ) previewedItem = item } } else { previewedItem = null } }, ... } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: NavigationBarItem( modifier = Modifier .onFocusChanged { if (it.isFocused) { scope.launch { delay( viewConfiguration.longPressTimeoutMillis ) previewedItem = item } } else { previewedItem = null } }, ... } CODE_BLOCK: NavigationBarItem( modifier = Modifier .onFocusChanged { if (it.isFocused) { scope.launch { delay( viewConfiguration.longPressTimeoutMillis ) previewedItem = item } } else { previewedItem = null } }, ... } CODE_BLOCK: NavigationBarItem( modifier = Modifier ... .semantics { customActions = listOf( CustomAccessibilityAction( label = "Preview item", action = { scope.launch { previewedItem = item } true } ) ) }, ... } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: NavigationBarItem( modifier = Modifier ... .semantics { customActions = listOf( CustomAccessibilityAction( label = "Preview item", action = { scope.launch { previewedItem = item } true } ) ) }, ... } CODE_BLOCK: NavigationBarItem( modifier = Modifier ... .semantics { customActions = listOf( CustomAccessibilityAction( label = "Preview item", action = { scope.launch { previewedItem = item } true } ) ) }, ... } - Beyond Font Scaling: Large Content Viewer with Compose - Github Gist