Slide 1

Slide 1 text

Supporting keyboard navigation with Compose DroidKaigi 2024 Tiphaine Handling navigation focus in 2024

Slide 2

Slide 2 text

2 Tiphaine ● Android engineer @ DeNA (Pococha) ● DroidKaigi volunteer staff ● Mobile Dev Japan co-organizer

Slide 3

Slide 3 text

3 Slides & Video: It's 2021, let's support accessibility (Japanese only) Better late than never ✌🤠✌ Today is a sequel to this talk

Slide 4

Slide 4 text

4 01 Introduction to Focus Common Problems 02 Focus in Compose 03

Slide 5

Slide 5 text

01 5 Introduction to Focus

Slide 6

Slide 6 text

6

Slide 7

Slide 7 text

7

Slide 8

Slide 8 text

8 Focus Navigation

Slide 9

Slide 9 text

9 ● Alternative way to interact with a smartphone without using the touch screen ● Move through the UI one element at a time Focus Navigation Focus Elements Ref: Google Play Store

Slide 10

Slide 10 text

Focus Focus 10 ● Current position in the UI ● Can only interact with this element ● Element will have some visual changes when focused Ref: Google Play Store

Slide 11

Slide 11 text

Focus 11 ● Accessibility Focus ≠ Navigation Focus ● Navigation Focus is sometimes called “Keyboard Navigation” ● Both focuses are Focus Navigation variants 😵💫 Focus Types

Slide 12

Slide 12 text

12 Differences between focuses

Slide 13

Slide 13 text

Don’t touch the screen 👋 👀 Don’t see the screen 13 Navigation Focus Accessibility Focus

Slide 14

Slide 14 text

Only actionable elements All elements Navigation Focus Accessibility Focus 14

Slide 15

Slide 15 text

focusable attribute importantForAccessibility and focusable attributes 15 Only actionable elements All elements Navigation Focus Accessibility Focus

Slide 16

Slide 16 text

Keyboard, Switch Access, D-pad (arrow keys)… Screen readers like TalkBack 16 focusable attribute importantForAccessibility and focusable attributes Only actionable elements All elements Navigation Focus Accessibility Focus

Slide 17

Slide 17 text

17 ● Hardware keyboard (tablets, Chromebooks) ● On-screen keyboard (soft input keyboard) Image: Sergi Kabrera Devices for Nav Focus

Slide 18

Slide 18 text

18 ● TV remote controller ● Smartwatches rotating bezel ● Game controller ● Switch Access Image: Erik Mclean Devices for Nav Focus

Slide 19

Slide 19 text

19 Changeable indicator Only one default indicator Keyboard, Switch Access, D-pad (arrow keys)… Screen readers like TalkBack focusable attribute importantForAccessibility and focusable attributes Only actionable elements All elements Navigation Focus Accessibility Focus

Slide 20

Slide 20 text

20 Focus indicator Navigation Focus Accessibility Focus

Slide 21

Slide 21 text

21 Due to the time restriction, today’s focus will be Nav Focus (🥶)

Slide 22

Slide 22 text

22 For more info on Accessibility Focus, check out my 2021 talk 💁 Slides & Video: It's 2021, let's support accessibility (Japanese only)

Slide 23

Slide 23 text

23 Types of navigation (relying on Nav Focus)

Slide 24

Slide 24 text

24 ● Using a D-pad (directional pad) or arrow keys ● Two-dimensional navigation ● Focus goes up, down, left, or right Directional Navigation

Slide 25

Slide 25 text

25 ● Using “tab” key or Switch Access ● One-dimensional navigation (forward or backward) ● Focus follows the order in which elements appear in the layout Tab Navigation 1 2 3

Slide 26

Slide 26 text

26 Focus Navigation Accessibility Focus Navigation Focus Directional Navigation Tab Navigation

Slide 27

Slide 27 text

01 27 Introduction to Focus Common Problems Focus in Compose 02 03 Introduction to Focus

Slide 28

Slide 28 text

02 28 Common Problems

Slide 29

Slide 29 text

1/3 29 Can all actions be done?

Slide 30

Slide 30 text

30 ● Make actionable elements focusable (if you don’t use OnClickListener) // Also focusable with // Accessibility Focus android:focusable="true" Handle click actions

Slide 31

Slide 31 text

myView.setOnLongClickListener { // Do something true } 31 Can’t reach the view 🥲

Slide 32

Slide 32 text

myView.setOnLongClickListener { // Do something true } // Set this for any listener 
 // except setOnClickListener⚠ myView.isFocusable = true 32

Slide 33

Slide 33 text

33 How about gesture-based actions? 🤔

Slide 34

Slide 34 text

34 Focusable, but nothing happens 😢 true } myView.isFocusable = true view.performClick() myView.setOnTouchListener { view, event -> // Do something with the event

Slide 35

Slide 35 text

35 ● Nav Focus can’t trigger OnTouchListener 😣 ● Need a different way to detect input events 👉 Listen to KeyEvents instead Handle gesture-based actions

Slide 36

Slide 36 text

myView.setOnKeyListener { view, keyCode, event -> 36 } // Choose which one(s) you want to support KeyEvent.KEYCODE_ENTER, ... -> { // Do something true } else -> false when (keyCode) { Ref: Android Developers }

Slide 37

Slide 37 text

myView.setOnKeyListener { view, keyCode, event -> } when (keyCode) { } 37 Don’t forget to parse duplicate events ⚠ when (keyCode) { ... } // The same event will come several times if (event.action != KeyEvent.ACTION_UP) { return@setOnKeyListener false }

Slide 38

Slide 38 text

override fun onKeyUp(keyCode: Int, event: KeyEvent?)
 : Boolean { } return when (keyCode) { KeyEvent.KEYCODE_ENTER -> performClick() else -> ... } 38 Use this instead for custom Views 🚀

Slide 39

Slide 39 text

39 Is it easy to use and navigate the app? 2/3

Slide 40

Slide 40 text

40 ● Skip non-interactive elements ● Wait for an action to initiate changes // Skip this View android:focusable=“false” Meet the user’s expectations

Slide 41

Slide 41 text

41 ● Make sure the focus can always be removed ● Verify the Esc key allows to move away ● Double check your WebViews, scrolls and drop-down lists Prevent keyboard traps

Slide 42

Slide 42 text

42 Try to test on different manufacturers 😇

Slide 43

Slide 43 text

43 ● Keep the flow logical and consistent // Tab navigation
 android:nextFocusForward=“@id/...” // Directional navigation // Can also set up, down and left android:nextFocusRight=“@id/...” Maintain the expected flow

Slide 44

Slide 44 text

44 Facilitate navigation Ref: Android Developers // Available from API 26+ android:keyboardNavigationCluster ● Group elements with clusters 1 2 3 4 10 11 12 Main content Bottom nav bar Top tabs

Slide 45

Slide 45 text

45 ● Request the focus when the start is obvious // Available from API 26+ // Won’t show on-screen keyboard Facilitate navigation

Slide 46

Slide 46 text

46 Ref: Alex Zlatkus Start here instead

Slide 47

Slide 47 text

47 Is the focus always visible? 3/3

Slide 48

Slide 48 text

48 ● Ensure the focus is visible ● Customize the indicator color and/or shape when necessary Don’t let the user get lost Ref: Google Play Store app in 2021

Slide 49

Slide 49 text

Customize the indicator: whole app 49 ... <item name="colorControlHighlight">...</item> Customize the indicator

Slide 50

Slide 50 text

50 ... Customize the indicator: single View ᶃ Customize the indicator: single View Customize the indicator

Slide 51

Slide 51 text

51 Customize the indicator: single View ᶄ Customize the indicator: single View Customize the indicator

Slide 52

Slide 52 text

// Set click effect as state_pressed in selector 52 Customize the indicator: single View ᶅ Customize the indicator: single View Customize the indicator

Slide 53

Slide 53 text

53 I will talk about on-screen keyboard another time… 🥲

Slide 54

Slide 54 text

01 54 Introduction to Focus Common Problems Focus in Compose 02 03 Introduction to Focus Common Problems

Slide 55

Slide 55 text

03 55 Focus in Compose

Slide 56

Slide 56 text

56 Compose specificities

Slide 57

Slide 57 text

57 ● Focus follows the declaration order of Composables ● Priorities goes to elements inside the same level Tab Navigation flow // Level ᶃ // Level ᶄ // Level ᶄ Column { Row { Row { } } } ... ...

Slide 58

Slide 58 text

58 CustomButton("1") CustomButton("2") CustomButton("3") CustomButton("4") Column { } Row { Row { } }

Slide 59

Slide 59 text

59 Row { Column { } Column { } } CustomButton("1") CustomButton("2") CustomButton("3") CustomButton("4")

Slide 60

Slide 60 text

60 Modifier order matters 🧐

Slide 61

Slide 61 text

61 Handling actions (in Compose)

Slide 62

Slide 62 text

Modifier.focusable() 62 ● Need to provide a focus indicator and handle the displaying logic by yourself 😭 Make the element focusable

Slide 63

Slide 63 text

63 Modifier var color by remember { .focusable() mutableStateOf(transparent) } // Modifier of your Composable Must set focusable as the last Modifier ⚠

Slide 64

Slide 64 text

64 Modifier .border(5.dp, indicatorColor) .onFocusChanged { focusState -> } color = if (focusState.isFocused) { black } else { transparent } var color by remember { ...} .focusable() 🎉

Slide 65

Slide 65 text

65 Modifier.clickable { // Do something } ● With this Modifier, the Composable will automatically become focusable Handle click actions

Slide 66

Slide 66 text

66 👀 How about other actionsʁ

Slide 67

Slide 67 text

67 Handle other actions 👉 Listen to KeyEvents instead (Again) ● Can’t trigger anything (including long click) 😢

Slide 68

Slide 68 text

68 Listen to KeyEvents Modifier.onPreviewKeyEvent {} Modifier.onKeyEvent {} // Parent callback is invoked first // Start from children callback

Slide 69

Slide 69 text

69 Modifier.onKeyEvent { keyEvent -> } when (keyEvent.key) { Modifier.onKeyEvent Key.Enter -> { // Do something true } else -> false }

Slide 70

Slide 70 text

70 // Parse duplicates if (keyEvent.type != KeyEventType.KeyUp) { return@onPreviewKeyEvent false } when (keyEvent.key) { } when (keyEvent.key) { ... } Modifier.onKeyEvent { keyEvent -> } Modifier.onKeyEvent

Slide 71

Slide 71 text

71 Controls & Navigation (in Compose)

Slide 72

Slide 72 text

● Can still be clicked in TouchMode 👋 72 Modifier.focusProperties { Skip an element canFocus = false }

Slide 73

Slide 73 text

73 Change the flow - Tab Navigation Modifier.focusProperties { } next = } previous = ... ... ● Use FocusProperties with FocusRequester Change the flow

Slide 74

Slide 74 text

74 val (first, second) = remember { FocusRequester.createRefs() }

Slide 75

Slide 75 text

Modifier 75 val (first, second) = remember { FocusRequester.createRefs() } first // First Composable // Second Composable (Destination) Modifier.focusRequester( ) ) second .focusRequester(

Slide 76

Slide 76 text

76 val (first, second) = remember { FocusRequester.createRefs() } Modifier .focusProperties { } second next = first) .focusRequester(

Slide 77

Slide 77 text

77 // Won’t be used The top most Modifier wins ⚠ Modifier .focusProperties { } .focusProperties { previous = second } next = second

Slide 78

Slide 78 text

78 Modifier.focusProperties { left = ... right = ... up = ... down = ... } Change the flow - Directional Nav Change the flow

Slide 79

Slide 79 text

79 val focusManager = LocalFocusManager.current Move the focus focusManager.moveFocus( ) FocusDirection.Down // Go to the nearest focusable Composable // Direction you want the focus to go

Slide 80

Slide 80 text

80 ● Go wherever you want (as long as it’s focusable) val requester = remember { FocusRequester() } requester.requestFocus() Request the focus

Slide 81

Slide 81 text

Button( onClick = { ) { ... } TextField(... // If clicked, move to the TextField 81 ) }

Slide 82

Slide 82 text

82 Button( onClick = { ) { ... } TextField(... } ) val requester = remember { FocusRequester() } Modifier.focusRequester(requester) // If clicked, move to the TextField

Slide 83

Slide 83 text

83 Button( onClick = { ) { ... } TextField(... } ) val requester = remember { FocusRequester() } Modifier.focusRequester(requester) requester.requestFocus() // If clicked, move to the TextField

Slide 84

Slide 84 text

84 Try it out!

Slide 85

Slide 85 text

85 ᶃ Plug it to your phone ᶄ Tap any key ᶅ The focus indicator will appear (Same steps with other external tools 🎮) With a hardware keyboard

Slide 86

Slide 86 text

86 1⃣ Use an emulator OR 2⃣ Use device mirroring With your computer keyboard

Slide 87

Slide 87 text

87 ● D-pad navigation is enabled by default with pre-set hardware profiles ✅ ● For custom profiles, select the option when creating the device 1⃣ Use an emulator

Slide 88

Slide 88 text

88

Slide 89

Slide 89 text

89

Slide 90

Slide 90 text

90

Slide 91

Slide 91 text

91 Control your phone with your computer keyboard 🥳

Slide 92

Slide 92 text

92 ● Use a real device through AndroidStudio ● Preferences → Tools → Device mirroring 2⃣ Use device mirroring

Slide 93

Slide 93 text

93

Slide 94

Slide 94 text

94 Quickly test on real devices while coding 🎉

Slide 95

Slide 95 text

95 ● Focus in Compose ɹ- Android Developers official documentation ● Android App Development: Accessibility ɹ(LinkedIn Learning) - Renato Iwashima References

Slide 96

Slide 96 text

Thanks! Theme: SlidesCarnival ɹ tahia910 Special thanks: ● Shogo Takasaki ● Hidenari Tajima