https://kotlinlang.org logo
#compose
Title
# compose
d

dorche

02/07/2023, 10:55 AM
Hi all, we have a requirement where we need to automatically focus the first field as we page through
HorizontalPager
(i.e. login form where you input details on a couple “pages”). I can’t seem to find a property that will notify me that the pager is done changing pages/animating so I can request focus for TextField X. Can’t just do a launched effect in the “page” composable either because the pager composes the next page too so that it can animate smoothly. Any ideas?
Copy code
HorizontalPager(
    count = Page.total(),
    state = pagerState,
    userScrollEnabled = false
) { page ->
    page(
        page = page,
        onForward = {
            pagerState.animateScrollToPage(pagerState.currentPage + 1)
        },
        onBack = onBack,
    )
}
page() here is the column that has the TextField
Copy code
page: @Composable (
        page: Int,
        onForward: () -> Unit,
        onBack: () -> Unit,
    ) -> Unit
r

Rebecca Franks

02/07/2023, 11:03 AM
If you are using androidx pager, you can watch
PagerState.settledPage
to know if its settled on the page or not... Otherwise for accompanist you can try something like this https://google.github.io/accompanist/pager/#reacting-to-page-changes ?
d

dorche

02/07/2023, 11:04 AM
generally, focusing a TextField “automatically” for the user as they navigate forward seems quite hard but it makes the user experience so much better because of this comment on Focus Requester
Copy code
FocusRequester is not initialized. Here are some possible fixes:

   1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
   2. Did you forget to add a Modifier.focusRequester() ?
   3. Are you attempting to request focus during composition? Focus requests should be made in
   response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
"""

/**
 * The [FocusRequester] is used in conjunction with
 * [Modifier.focusRequester][androidx.compose.ui.focus.focusRequester] to send requests to
 * change focus.
 *
 * @sample androidx.compose.ui.samples.RequestFocusSample
 *
 * @see androidx.compose.ui.focus.focusRequester
 */
class FocusRequester {
I am using the accompanist one (we have a Compose-only app), could you send a link to the AndroidX one, have I missed something obvious here? I don’t think I can and should this the way Accompanist suggests because there are these comments on the fields in PagerState:
Copy code
/**
     * The index of the currently selected page. This may not be the page which is
     * currently displayed on screen.
     *
     * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
     */
    @get:IntRange(from = 0)
    var currentPage: Int
        get() = _currentPage
        internal set(value) {
            if (value != _currentPage) {
                _currentPage = value
                if (DebugLog) {
                    Napier.d(message = "Current page changed: $_currentPage")
                }
            }
        }
Copy code
/**
     * The target page for any on-going animations or scrolls by the user.
     * Returns the current page if a scroll or animation is not currently in progress.
     */
    @Deprecated(
        "targetPage is deprecated in favor of currentPage as currentPage property is" +
            "now being updated right after we over scrolled the half of the previous current page." +
            "If you still think that you need targetPage, not currentPage please file a bug as " +
            "we are planning to remove this property in future.",
        ReplaceWith("currentPage")
    )
    val targetPage: Int
        get() = animationTargetPage
            ?: flingAnimationTarget?.invoke()
            ?: when {
                // If a scroll isn't in progress, return the current page
                !isScrollInProgress -> currentPage
                // If the offset is 0f (or very close), return the current page
                currentPageOffset.absoluteValue < 0.001f -> currentPage
                // If we're offset towards the start, guess the previous page
                currentPageOffset < 0f -> (currentPage - 1).coerceAtLeast(0)
                // If we're offset towards the end, guess the next page
                else -> (currentPage + 1).coerceAtMost(pageCount - 1)
            }
If I were to collect that
currentPage
value in a snapshotFlow, surely that will emit too early and I will fall into problem number 3 again of the FocusRequester class comment
Found the Foundation one and
settledPage
https://android-review.googlesource.com/c/platform/frameworks/support/+/2232050 This might work loading
Foundation lib is the way to go, thanks Rebecca! shame we have to go to alpha again but oh well
r

Rebecca Franks

02/07/2023, 4:34 PM
Great 🎉 Pager in accompanist will be deprecated soon anyway in favor of androidx version
54 Views