Slide 1

Slide 1 text

Handling focus in 2024 Supporting different navigation ways with Compose Tiphaine (DeNA) AndroidMakers by droidcon 2024

Slide 2

Slide 2 text

2 ● Android developer @ DeNA (Pococha) ● DroidKaigi volunteer staff ● Accessibility & privacy 🔥 Hello! I am Tiphaine ɹ tahia910

Slide 3

Slide 3 text

01 3 Introduction Checklist Focus with Compose 02 03

Slide 4

Slide 4 text

01 4 Introduction

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

6

Slide 7

Slide 7 text

7 Focus Navigation

Slide 8

Slide 8 text

8 RefɿAkela NDE

Slide 9

Slide 9 text

9 ● Alternative way to interact with your phone without using the touch screen ● Move through the UI one element at a time Focus Navigation

Slide 10

Slide 10 text

Focus Navigation 10 Focus ● Current position in the UI ● Can only interact with this element ● Element will have some visual changes when focused

Slide 11

Slide 11 text

Focus in Android world 11 ● Accessibility focus vs Navigation focus ● Navigation focus is sometimes
 called Keyboard Navigation ● Both focuses are Focus Navigation variants 😵💫 Focus

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

Accessibility Focus Only actionable elements All elements Navigation Focus Accessibility Focus 14

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

16 Navigation Focus Accessibility Focus Only actionable elements All elements focusable attribute focusable and importantForAccessibility attributes Keyboard, d-pad, switch… Screen readers like TalkBack

Slide 17

Slide 17 text

17 Devices used for navigating ● Hardware keyboard (tablets, Chromebooks) ● On-screen keyboard (soft input keyboard) PhotoɿSergi Kabrera

Slide 18

Slide 18 text

18 ● TV remote controller ● Smartwatches rotating bezel ● Game controller ● Switch Access RefɿGoogle Devices used for navigating

Slide 19

Slide 19 text

19 focusable and importantForAccessibility attributes Changeable indicator Only one default indicator Only actionable elements All elements focusable attribute focusable and importantForAccessibility attributes Keyboard, d-pad, switch… Screen readers like TalkBack Navigation Focus Accessibility Focus

Slide 20

Slide 20 text

20 Focus indicator Navigation Focus Accessibility Focus

Slide 21

Slide 21 text

21 Due to time restriction, I will skip accessibility focus

Slide 22

Slide 22 text

22 Today’s focus will be navigation focus (🥶)

Slide 23

Slide 23 text

23 Types of navigation (relying on nav focus)

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

25 Tab navigation ● Using Switch Access or “tab” key on a keyboard. ● One-dimensional nav (forward or backward) ● Focus follows the order in which elements appear in the layout (top left to bottom right)

Slide 26

Slide 26 text

26 Focus Navigation Accessibility Focus Navigation Focus Directional Navigation Tab Navigation (?) 🤔

Slide 27

Slide 27 text

27 01 Introduction Checklist Focus with Compose 02 03

Slide 28

Slide 28 text

02 28 Checklist

Slide 29

Slide 29 text

● Do all actions ● Navigate without keyboard traps ● See which element is being focused ● Skip non-interactive elements 29 Users must be able to:

Slide 30

Slide 30 text

30 ● Make actionable elements focusable, especially if you don’t use OnClickListener // Focusable with all focuses android:focusable=“true" Ensure clickability

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⚠ timelineItem.isFocusable = true 32

Slide 33

Slide 33 text

33 Ensure clickability ● Verify that all ClickableSpans are reachable

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

35 ● Make sure the focus is always moveable ● Verify the Return key allows to move away ● Double check your WebViews, scrolls 
 and drop-down lists Avoid keyboard traps

Slide 36

Slide 36 text

36 ● Ensure the flow is logical and consistent Facilitate navigation // Tab navigation
 android:nextFocusForward=“@id/...” // Directional navigation // Can also set up, down and left android:nextFocusRight=“@id/...”

Slide 37

Slide 37 text

37 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 38

Slide 38 text

38 ● Prevent the on-screen keyboard to appear on top of the text field being focused Optimize text input RefɿAndroid Developers, Lua Software ?

Slide 39

Slide 39 text

39 ● Request the focus when the start is obvious Optimize text input // Available from API 26+ // Won’t show on-screen keyboard

Slide 40

Slide 40 text

40 RefɿAlex Zlatkus Start here instead

Slide 41

Slide 41 text

41 ● 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 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

44 Customize the indicator: single View ᶄ Customize the indicator: single View Customize the indicator

Slide 45

Slide 45 text

45 Customize the indicator: single View ᶅ Customize the indicator: single View Customize the indicator

Slide 46

Slide 46 text

46 Give some time ● Stop motions when focused ● Get recommended time out to set elements that disappear

Slide 47

Slide 47 text

47 a11yManager val a11yManager = ContextCompat.getSystemService( requireContext(), AccessibilityManager::class.java, ) RefɿAung Kyaw Paing, Suchi Bansal

Slide 48

Slide 48 text

// Can combine with other flags: // FLAG_CONTENT_ICONS, FLAG_CONTENT_TEXT AccessibilityManager.FLAG_CONTENT_CONTROLS, // The original timeout you planned defaultTimeOut, 48 // Available from API 29+ a11yManager?.getRecommendedTimeoutMillis( ) RefɿAung Kyaw Paing, Suchi Bansal a11yManager

Slide 49

Slide 49 text

49 How about 
 gesture-based actions? 🤔

Slide 50

Slide 50 text

50 true } myView.isFocusable = true myView.setOnTouchListener { view, event -> // Do something with touch event

Slide 51

Slide 51 text

51 Focusable, no warning, 
 but still not working :( true } myView.isFocusable = true view.performClick() myView.setOnTouchListener { view, event -> // Do something with touch event

Slide 52

Slide 52 text

52 ● performClick() just executes the code set in a separate OnClickListener ● “ClickableViewAccessibility” warning is for accessibility focus Handle gesture-based actions

Slide 53

Slide 53 text

53 ● Nav focus can't even trigger OnTouchListener 😢 ● Need a different way to detect input events Handle gesture-based actions 👉 Listen to KeyEvents instead

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

myView.setOnKeyListener { view, keyCode, event -> } when (keyCode) { } 55 Parse duplicate events ⚠ when (keyCode) { ... } // Handle the event only once if (event.action != KeyEvent.ACTION_UP) { return@setOnKeyListener false }

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

57 Try to test on different manufacturers 🧐

Slide 58

Slide 58 text

58 01 Introduction Checklist Focus with Compose 02 03

Slide 59

Slide 59 text

03 59 Focus with Compose

Slide 60

Slide 60 text

60 Compose specificities

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

64 🥲 Modifier order matters (a lot)

Slide 65

Slide 65 text

65 Handling focus (in Compose)

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

69 Modifier.clickable { // Do something } ● Composable is focusable by default when using the default click listener ✅ Handle click actions

Slide 70

Slide 70 text

70 👀 How about
 OnLongClickListener?

Slide 71

Slide 71 text

Modifier.combinedClickable( ) 71 onClick = { }, // Not triggered 😢 // Mandatory & non-nullable onLongClick = { },

Slide 72

Slide 72 text

72 Handle other actions 👉 Listen to KeyEvents instead (again)

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

76 RefɿAung Kyaw Paing val a11yManager = LocalAccessibilityManager.current Get the recommended time out a11yManager

Slide 77

Slide 77 text

77 RefɿAung Kyaw Paing a11yManager?.calculateRecommendedTimeoutMillis( ) a11yManager Get the recommended time out originalTimeoutMillis = defaultTimeOut, containsIcons = false, containsText = false, containsControls = true, // Returns a Long value

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

85 val focusManager = LocalFocusManager.current focusManager.moveFocus(...) Move the focus ● Go to the nearest (focusable) Composable

Slide 86

Slide 86 text

86 TextField(... keyboardOptions = KeyboardOptions( ) ), imeAction = ImeAction.Next,

Slide 87

Slide 87 text

87 keyboardActions = KeyboardActions( onNext = { } ), ), val focusManager = LocalFocusManager.current TextField(... keyboardOptions = KeyboardOptions(... ) focusManager.moveFocus( ) FocusDirection.Down

Slide 88

Slide 88 text

88 Move the focus ● Go wherever you want (as long as it’s focusable) Move the focus - the other way val requester = remember { FocusRequester() } requester.requestFocus()

Slide 89

Slide 89 text

89 Button( onClick = { ) { ... } TextField(... } ) val requester = remember { FocusRequester() } Modifier.focusRequester(requester) requester.requestFocus()

Slide 90

Slide 90 text

90 Try it out!

Slide 91

Slide 91 text

91 ● Plug it to your phone ● Tap any key to make the indicator appear ● 🎉 ● Can also use other tools with D-pad 🎮 Use a hardware keyboard

Slide 92

Slide 92 text

92 1⃣ Set up an emulator OR 2⃣ Enable device mirroring Use your laptop keyboard

Slide 93

Slide 93 text

93 ● D-pad navigation is enabled by default with pre-set hardware profiles ● For new profiles, select the option when creating the device 1⃣ Set up an emulator

Slide 94

Slide 94 text

94

Slide 95

Slide 95 text

95

Slide 96

Slide 96 text

96

Slide 97

Slide 97 text

97

Slide 98

Slide 98 text

98

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

100

Slide 101

Slide 101 text

101 Quickly test on real devices while coding 🎉

Slide 102

Slide 102 text

102 ● Focus in Compose - Android Developers official documentation ● Android App Development: Accessibility - Renato Iwashima References

Slide 103

Slide 103 text

Thanks! ThemeɿSlidesCarnival ɹ tahia910 Special thanks: ● DeNA ● Kimitoshi Mizutani ● Shota Moriwaki ● Yosuke Miyanishi