Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Worry free TV App development for users and dev...

Avatar for Daichi Takeya Daichi Takeya
September 17, 2025

Worry free TV App development for users and developers - a focus on Compose internal implementation

Avatar for Daichi Takeya

Daichi Takeya

September 17, 2025
Tweet

More Decks by Daichi Takeya

Other Decks in Technology

Transcript

  1. Worry free TV App development for users and developers ~

    a focus on Compose internal implementation ~ DroidKaigi 2025 - 2025/09/11 Daichi Takeya
  2. Self Introduction • Daichi Takeya (X:@taked_oO, GitHub:@taked137) ◦ April 2023

    ~ CyberAgent, Inc. ◦ May 2023 ~ AbemaTV, Inc. - Native Mobile Team ◦ May 2024 ~ AbemaTV, Inc. - Native AndroidTV Team
  3. Table of Contents 1. What is Compose for TV? 2.

    Style settings for TV apps 3. Controlling focus movement a. Automatic control b. Manual control 4. Controlling scrolling 5. Impressions of actually implementing it
  4. Table of Contents 1. What is Compose for TV? 2.

    Style settings for TV apps 3. Controlling focus movement a. Automatic control b. Manual control 4. Controlling scrolling 5. Impressions of actually implementing it
  5. • Operate using a remote control TV App Operation "TV

    Navigation Controller" by Android Developers, licensed under CC BY 2.5. ・Emphasize the focused item ・Automatically scroll with focus Cannot operate as intuitively as mobile apps Focus operability greatly affects user experience
  6. • A library based on Android View that improves focus

    operability Leanback Library Since UI is drawn imperatively, unintended state update omissions are prone to occur. We don’t want to touch Adapter even in 2025. 😤😤😤 RecyclerView Leanback Nevertheless…
  7. • Compose for TV has two libraries: ◦ tv-material (🎉now

    stable) ◦ tv-foundation (🚛migrating to the stable “compose-foundation”) Migrating from Leanback to Compose for TV Leanback Compose I can’t tell what’s focused, and vertical scrolling is jerky You needs to figure it out yourself. (OMG😱) Let's understand it completely today Is the era of Compose for TV here?!
  8. Table of Contents 1. What is Compose for TV? 2.

    Style settings for TV apps 3. Controlling focus movement a. Automatic control b. Manual control 4. Controlling scrolling 5. Impressions of actually implementing it
  9. Applying styles for TV apps • First, emphasize the focused

    items We can concisely set this using Composable from androidx.tv.material3, e.g., Surface, Card, etc… It’s now easier to find focused items 🎉 We could try hard with Modifier.onFocusChanged, but... Now that it’s easier to see, let’s move on to the main topic
  10. Table of Contents 1. What is Compose for TV? 2.

    Style settings for TV apps 3. Controlling focus movement a. Automatic control b. Manual control 4. Controlling scrolling 5. Impressions of actually implementing it
  11. Default focus movement in Compose • The closest View in

    the direction of the input key is focused. Actual focus movement Focus destination corresponding to key direction For simple screens, the default behavior is sufficient💯 Everyone is happy
  12. Quiz on Default Focus Movement Assume each item is focusable,

    and a red border will be displayed when focused.
  13. Quiz on Default Focus Movement Q. When ‘→’ is pressed

    on the orange item, what will be focused next?
  14. Quiz on Default Focus Movement Q. When ‘→’ is pressed

    on the orange item, what will be focused next? It’s me!!!
  15. Default Focus Control Logic Deep Dive Selection criteria: positioned to

    the ”right” of the focused View while not extending past the “left” edge. ❌ Priority selection: overlapping “Y-coordinates” with the focused View. ⭕ ⭕ Other Views feel like they are positioned “vertically” rather than “horizontally”👏 • Why does pressing ‘→’ focus on the top right item (blue)?
  16. Default Focus Control Logic Deep Dive Selection criteria: positioned to

    the ”right” of the focused View while not extending past the “left” edge. ❌ Priority selection: overlapping “Y-coordinates” with the focused View. ⭕ ⭕ Other Views feel like they are positioned “vertically” rather than “horizontally”👏 • Why does pressing ‘→’ focus on the top right item (blue)? Focus control is also managed through many other sophisticated logic systems.
  17. Limitations of default focus control • Focus movement purely based

    on View positions ◦ Does not consider Component units such as Column or Row The first item is not focused initially Master/Detail Flow screen Goes back to another tab Screen used in quiz Want to change the focus target when ‘→’ is pressed
  18. Approaches to Focus Control 🤖 Automatic Control ・Can be completed

    by adding just a few lines ・Compose encapsulates the implementation ・This is sufficient in most cases ・Limited in the problems it can address ・Bugs exist depending on the Compose version 🎯 Manual Control ・(Probably) can handle all cases ・Poor management leads to unexpected behavior ・Readability significantly degrades Let's adopt the appropriate approach and aim for an app that is friendly to both users and developers.
  19. Table of Contents 1. What is Compose for TV? 2.

    Style settings for TV apps 3. Controlling focus movement a. Automatic control b. Manual control 4. Controlling scrolling 5. Impressions of actually implementing it
  20. Modifier.focusGroup() --- The foundation supporting focus control • Groups child

    components as a “focus exploration unit” ◦ When focus moves, it prioritizes exploring elements within the group default focusGroup Grouping buttons in the red area Focus destination when ‘←’ is pressed (elements within the group are preferentially selected) Though seemingly subtle, it is essential for focus control
  21. (Aside) Composables that are focusGroup by default • LazyColumn, LazyRow,

    etc., are focusGroup by default What if they weren’t focusGroup? LazyRow focus movement Undrawn child elements are also considered as focus targets → The program guide can be scrolled🙌 Focus targets are selected based on the positional relationship of currently drawn elements → The program guide cannot be scrolled😢
  22. Intuitive UX through “Saving/Restoring” Focus Position In Compose, it is

    necessary to implement separate logic for restoring focus position Focus movement based solely on View positions 😚 Move seamlessly between adjacent tabs → Focus moves intuitively 😒 Suddenly moves to a tab with no context → Unexpected behavior Retore focus position
  23. Modifier.focusRestorer --- Saving/Restoring Focus Position • Saves/restores focus position for

    child elements of focusGroup ・Just add it to Modifier ・Finally stable in compose-ui 1.8.0 LazyColumn is already focusGroup, so specify only focusRestorer. For Column, apply “focusRestorer → focusGroup” in that order
  24. Behavior after applying Modifier.focusRestorer Set focusRestorer on both the left

    and right Columns Focus position is restored 👍 Even if the list is updated, the previously saved state is reused 😑 A mechanism is needed to discard the saved focus states
  25. • Initialize the state of focusRestorer ◦ It is not

    initialized by just updating the UI compose.runtime.key --- Inducing Composition Rebuilding If another tab on the left is selected, regenerate the entire composition tree Along with the list update, the state of focusRestorer is also initialized 👍 After updating the list, the focus target is selected based on the display position😑 I want the first item to be focused after the UI update
  26. Specifying fallback target in focusRestorer • Focus target when focus

    restoration fails, such as for the first item Specify the first focusable element in the list as the fallback target Marker to identify the focus target 🎉 FocusThat won’t be lost • Focus position restoration • After list update, focus on the first item
  27. Automatic control summary Group child elements as a “focus exploration

    unit” ・Prioritizes selecting focus targets within the group ・Achieves group-level control (such as restoring focus target) ・Lazy Layout is focusGroup by default Modifier.focusGroup Modifier.focusRestorer Restores focus targets within the same group ・Use in conjunction with Modifier.focusGroup ・Reset focus information when UI changes (using `key`) ・Speficy fallback destination using FocusRequester
  28. Automatic control summary Group child elements as a “focus exploration

    unit” ・Prioritizes selecting focus targets within the group ・Achieves group-level control (such as restoring focus target) ・Lazy Layout is focusGroup by default Modifier.focusGroup Modifier.focusRestorer Restores focus targets within the same group ・Use in conjunction with Modifier.focusGroup ・Reset focus information when UI changes (using “key”) ・Speficy fallback destination using FocusRequester In addition to this, I want more flexible focus control!
  29. Table of Contents 1. What is Compose for TV? 2.

    Style settings for TV apps 3. Controlling focus movement a. Automatic control b. Manual control 4. Controlling scrolling 5. Impressions of actually implementing it
  30. Cases requiring manual control Avoiding unintended state destruction Changing Composable

    to be unfocusable When hidden by AnimatedVisibility, focusRestorer’s state is also destroyed. The Button can still receive focus even when `enabled = false` Changing focus destination ❌ The focus destination when the ‘→’ key is pressed determined by Compose All solved with a certain Modifier
  31. Modifier.focusProperties --- Setting to be unfocusable • Overwrite a Composable

    that is internally set as focusable to be unfocusable Even disabled Buttons are focusable 😢 The canFocus property of focusProperties excludes it from candidates for focus🎉
  32. Modifier.focusProperties --- D-pad focus movement control How to use focusProperties

    Finely control the destination of key operations for each component Change the focus target Focus on fr (purple) when right key is pressed
  33. Modifier.focusProperties --- Controlling behavior for focusGroup contents • Setting focus

    destination when entering/exiting focusGroup (If you try hard) can substitute focusRestorer • “Focus state” can be managed in any group ◦ No need discard the entire CompositionTree with `key` ◦ Can also be saved outside AnimatedVisibility enter/onEnter exit/onExit
  34. (Aside) Restorering focus position with FocusRequester Focus position of focusGroup

    can be saved/restored at any time (focusRestorer performs these actions on enter/exit)
  35. Cautions for manual control (especially focusProperties) Implementing with focusProperties Doubles

    the code 😫 Do not easily introduce; consider importance as a product requirement!
  36. Manual control summary • Use Modifier.focusProperties as a last resort

    👍 Fine-grained control over behavior • Set Composable as non-focusable • Customize focus destination • Alternative to focusRestorer ◦ Manage “UI” and “state” independently 🥶 Implementation complexity • Logic becomes difficult to understand even for implementers • Risk of introducing bugs Focus distination customization Override focus availability of Composables
  37. Table of Contents 1. What is Compose for TV? 2.

    Style settings for TV apps 3. Controlling focus movement a. Automatic control b. Manual control 4. Controlling scrolling 5. Impressions of actually implementing it
  38. Scrolling in TV Apps Scrolling automatically occurs with focus movement

    (If using a touch-enabled device) Scrolling via swipe gestures is also possible “Focus navigation” and “Swipe gestures” execute different scrolling logic Intended to disable scrolling, but only “swipe gesture” scrolling was disabled 😔This often happens as well…
  39. Making containers scrollable • Modifier.verticalScroll / Modifier.horizontalScroll enables scrolling Includes

    logic for both mobile and TV • 📱scrolling via swipe gestures • 📺scrolling with focus movement focusGroup is also automatically applied • ScrollableNode delegates to FocusTargetModifierNode, similar to FocusGroupNode
  40. • The top (left) edge of the focused item aligns

    30% of the parent Scroll amount associated with focus movement 30% 70%
  41. Problems with default scrolling If a button is focused, the

    title should be displayed It scrolls to 30% and the title is cut off Scrolling based only on the focused composable is insufficient 30%
  42. BringIntoViewSpec --- Controlling scroll amount associated with focus • Control

    scroll animation and scroll amount Returning 0 in calculateScrollDistance stops scrolling (Not stopped by LazyColumn’s userScrollEnabled )
  43. BringIntoViewSpec --- Controlling scroll amount associated with focus • Control

    scroll animation and scroll amount The control logic introduced in the migration guide Calculates the scroll amount from the positional relationship between parent and child
  44. Changing the scroll reference position 65% Specify the reference position

    with parentFraction, childFraction and provide it with CompositionLocal Although it no longer goes off-screen, the overall scrolling behavior has changed
  45. BringIntoViewModifierNode --- Changing the scroll logic for child elements Change

    the scroll reference position from “child node” to “parent node” Scroll so that the entire parent node is visible All coordinate information of the child node is ignored Solved the clipping issue without changing the overall scrolling behavior🎉
  46. Other Scroll Animations • APIs commonly used in mobile app

    development are also available NestedScroll can also be applied (Completely stopping scrolling) Scrolling by LazyListState.animateScrollToItem (* Focus position does not move)
  47. BringIntoViewSpec --- Changing Scroll Animation • Override the animationSpec of

    BringIntoViewSpec Smoothly ⚠ compose 1.8.x ignores this Snappily
  48. Summary of scroll control Change scroll amount and animation ・Distributed

    via CompositionLocal ・Position-based logic introduced officially is convenient ・Animation is ignored in compose 1.8.x ・Affects scrolling of the entire list BringIntoViewSpec Override scroll logic for child focus ・Intercepts child scroll requests for custom behavior handling ・Changing to show the entire parent prevents clipping ・Applicable only to specific items BringIntoViewModifierNode 65% Show entire parent Ignore child coordinates
  49. Table of Contents 1. What is Compose for TV? 2.

    Style settings for TV apps 3. Controlling focus movement a. Automatic control b. Manual control 4. Controlling scrolling 5. Impressions of actually implementing it
  50. Development Efficiency Improvements RowsSupportFragment DetailsSupportFragment PresenterSelector ListRow VerticalGridView WindowAlignment SingleRow

    etc… No Leanback learning curve required Previously struggled with Fragment in Fragment workarounds...😢 Eliminated imperative state update bugs Fixed ViewHolder reuse causing unintended state carryover
  51. Points to Note from Implementation Experience • Challenging to separate

    UI and focus control logic • FocusRequester-based processing is hard to read ◦ Requires effort to discover where the FocusRequester is attached • Crashes immediately when used before attachment (used to happen) ◦ Easy to fall into this trap with Lazy Layouts 🕹 Difficulty in Handling FocusRequester • Many experimental features with frequently changing behavior ◦ Scroll animations suddenly become ignored ◦ Processes that previously threw exceptions now only use println • Even stable features have lingering bugs ◦ e.g.) Focus jumping issues in LazyVerticalGrid (link) 🐝 Technical Instability Despite these challenging aspects, we feel that adopting Compose for TV has improved development efficiency through faster development speeds, reduced risk of bug introduction, and easier feature extensions.
  52. Conclusion • TV app focus navigation comfort significantly impacts user

    experience ◦ The Leanback library automatically applies logic to improve user experience ◦ With Compose for TV, you need to implement this functionality yourself • Several APIs are available for focus control - choose based on tradeoffs ◦ Automatic control: focusGroup, focusRestorer ◦ Manual control: focusProperties, focusRequester • Auto-scroll behavior accompanying "focus movement" can be customized ◦ Change reference position by providing BringIntoViewSpec via CompositionLocal ◦ Override logic using BringIntoViewModifierNode Compose for TV is great!