I recently had my Wear app rejected because my scr...
# compose-wear
j
I recently had my Wear app rejected because my scrollable content didn’t have scrollbars and “Missing rotary input”. The offending UI is using a vertical pager which unfortunately doesn’t support rotary input. My attempts at wiring it up failed, so I forwent it. It appears this req. behavior, so I’m wondering if I could achieve a similar UX of a vertical pager but with a LazyColumn/LazyScalingColumn instead? Does anyone have any thoughts, recommendations, feedback for having “separate screens” that depending on the Watch size, may need to be scrolled to see the full content, as well as support vertical scrolling to see the next screen?
y
The simplest approach should pass the review. Just use rotaryWithScroll. It will be missing some intelligent snap, so may feel broken compared to finger scrolling.
Cc @Michail Kulaga who might have ideas
j
How do I display a scrollbar using the VerticalPager w/
rotaryWithScroll(…)
?
y
PagerState is a ScrollableState
Pass into PositionIndicator/State
j
The following code doesn’t scroll vertically.
Copy code
val state = rememberPagerState(initialPage = gameTab.ordinal) { GameTab.count }
    VerticalPager(
        state = state,
        modifier = Modifier
            .rotaryWithScroll(scrollableState = state)
            .fillMaxSize()
            .testTag(UiTestingTags.HMOGameScreenPager),
        flingBehavior = PagerDefaults.flingBehavior(state = state),
    ) { page ->
        when (GameTab.fromInt(page)) {
            GameTab.Batter -> {
                HierarchicalFocusCoordinator(requiresFocus = { page == state.currentPage }) {
                    BatterView(onNavigateUp = onNavigateUp)
                }
            }

            GameTab.Score -> {
                HierarchicalFocusCoordinator(requiresFocus = { page == state.currentPage }) {
                    ScoreView( onNavigateUp = onNavigateUp)
                }
            }

            GameTab.Scoreboard -> {
                HierarchicalFocusCoordinator(requiresFocus = { page == state.currentPage }) {
                    ScoreboardView(
                        onNavigateUp = onNavigateUp,
                        onNavigateToEditGame = onNavigateToEditGame
                    )
                }
            }
        }
    }
}
y
I can prototype tomorrow. AFK
j
PageIndicator
takes a
ScrollState
which is a subclass of
ScrollableState
. PagerState subclasses
ScrollableState
only.
y
Ahhh, ok. I guess we'll need to implement PositionIndicatorState. Sorry
m
The problem might be with the focus support . Probably you need to request focus somewhere . Or you need to use HierarchicalFocusCoordinator on top of your code
j
I will try wrapping my code in a HierarchicalFocusCoordinator..
Should I always return true from the requiresFocus lambda? I’m not sure what else the return value should be.
Wrapping my above code in a
HierarchicalFocusCoordinator(_requiresFocus_ = { true }) {}
didn’t get rotary scrolling to work.
m
Can you try to put
false
into internal
HierarchicalFocusCoordinator
so that only the top one would have focus?
j
That got it to work
🔥 1
m
Why do you need to have them there in the first place? Do you want them to be scrollable as well?
☝🏻 1
j
The individual screens may need to vertically scroll on smaller watchs. Ideally, I would rather they didn’t.
y
nested scrolling will get messy
j
Do you have a recommended solution? I was thinking making the “screens” items in a LazyColumn and using snapping to achieve the same effect. Thoughts on that instead?
m
We don’t support nested scrolling yet in rotary modifiers. I think I got an idea of what you want to achieve.. We don’t support it yet
j
Since that behavior isn’t supported, should my screens only support internal vertical scrolling to reveal their full content and horizontal scrolling to to navigate between the “screens”?
y
Not sure what the correct code is, but something like this for the PositionIndicator. It's somewhat close...
Copy code
Scaffold(positionIndicator = {
        PositionIndicator(
            state = object : PositionIndicatorState {
                override val positionFraction: Float
                    get() = if (state.pageCount <= 1) {
                        1f
                    } else {
                        (state.currentPage + state.currentPageOffsetFraction) / (state.pageCount - 1)
                    }

                override fun sizeFraction(scrollableContainerSizePx: Float): Float =
                    1f / state.pageCount

                override fun visibility(scrollableContainerSizePx: Float): PositionIndicatorVisibility =
                    PositionIndicatorVisibility.Show
            },
            indicatorHeight = 50.dp,
            indicatorWidth = 4.dp,
            paddingHorizontal = 5.dp,
        )
    }) {
        VerticalPager(
🙌🏿 1
j
To illustrate what I’m trying to achieve, I’ve attached the following images of the 3 screens I want to vertically scroll between. As you can see the content “spills” beyond the screen bounds on round watch (especially SMALL_ROUND). I can ask my designer to tweak to make sure all content on display at once, but really seems like a limiting factor.
y
Can vertical Pager pages be taller than one screen. Or is that a limitation?
j
I don’t think I follow… Currently the pages are taller than the device screen.
y
So then isn't scrolling working across all screens as a single linear position?
j
With the new changes from above it scrolls the individual pages only.
But, I’m worried since the internal screen content doesn’t scroll with rotary it will get reject again.
m
@Jonathan i did the steps from this blog post to make the rotary inpit qork using a scaffold and a vertical lazylist in compose with wearos. https://dev.to/vnicius/rotary-input-in-wear-os-with-compose-1pb5
👏🏿 1
y
I'll flag your case internally. Not sure there was a conscious decision on scrolling for vertical pagers yet.
j
Thank you all for help. I have the rotary scrolling working (no snapping). I need to make a few more changes and I think it will be ready to for a new submission. @yschimke I tried to implement an
RotaryScrollAdapter
for the Pager but it appears the size returned from PagerState.layoutInfo.pagesize isn’t suitable for
RotaryScrollAdapter.averageItemSize
. Do you have an idea when official support will be added?
y
Misha would know better than me. But I'm guessing no public plans yet.
Some other options for this, not sure if they would be completely acceptable in reviews. 1. Use onRotaryInputAccumulated to change page like a stepper, with discrete events instead of smooth scrolling. 2. Use a VerticalPagerIndicator instead, maybe just the HorizontalPagerIndicator rotated?
m
@Jonathan IMO, with static content like yours on the screenshots it’s better to make everything fit into the screen without vertical scrolling . Small watch screen is not an exception, it’s a valid form factor and isn’t going anywhere. You also have a hamburger menu in the corner, which will be always cut out by the screen shape - generally we do not recommend it. Try to replace it with simpler navigation, or have a separate button with clear meaning
1
y
Here is a playground for the things discussed above https://github.com/google/horologist/pull/1935/files
🙌🏿 1
I couldn't get pages taller than a screen. Not sure how that is achieved with Pager.
Agreed a couple of experimental components with wear compose team PM. https://github.com/google/horologist/pull/1937
@Jonathan I'll echo what Misha said. if you are putting these inside a Vertical container, then making the content fit a single screen is critical. It might be worth testing on a small round emulator, with font scaling in settings set to max. Make sure it fits on a single screen even then. Or consider changing to a HorizontalPager, and then each page can scroll vertically.
j
I prefer making the content fit on one page, or a horizontal pager but my designer preferred the current design more. I will re-convene with them, and inform them that the current design conflicts with the preferred Google Play store design guildlines for Wear OS.
@yschimke I’m not able to import
AppScaffold
,
ScreenScaffold
. I have a dependency on horologist-compose-layout.
y
You don't need them. Can use any Scaffold really.
They are in the 0.5.16? Release
j
@yschimke I was able to finally test out your playground code. It works flawlessly. My issue with the imports was related to me using an older version of Horologist (0.5.14).
🎉 1
y
If you end up using this, please send me a link to your app in the play store (send me anyway!). Would be good to show the App to internal UX team for input into planning.
j
I plan to submit for review in the next two hours. I will send you the link once OI do.
👍 1
Is there any guidelines about having a picker in a vertically scrollable screen? My current UI is a little to large to have all content fit on-screen. The Wear App Quality guidelines say all scrollable content should be controllable via rotary input. I’m not sure how to pass focus back to Column after the user finishes selecting a value in a picker.