Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Declare and implement a new v2.ScrollbarAdapter #368

Merged
merged 11 commits into from
Jan 18, 2023

Conversation

m-sasha
Copy link
Member

@m-sasha m-sasha commented Jan 11, 2023

... in order to distinguish between scrollbar (track) size and scrollable viewport size.

Proposed Changes

  • Add a new ScrollbarAdapter interface in androidx.compose.foundation.v2
  • The new interface will provide contentSize and viewportSize. maxScrollOffset will be implemented as an extension function.
  • Add ScrollState.viewportSize in order to support the new ScrollbarAdapter implementation for a regular scrollable container.
  • Implement the new ScrollbarAdapter for ScrollState and for LazyListState.
  • Copy all public functions in androidx.compose.foundation.Scrollbar to the new androidx.compose.foundation.v2.Scrollbar and mark all the old ones as deprecated.
  • Provide full binary backwards compatibility using @JvmName and partial source backwards compatibility by naming the new functions the same as the old ones. The only exception is the scrollbar adapter interface itself, for which the users will need to change the import from androidx.compose.foundation to androidx.compose.foundation.v2

Testing

Test: Manually and with new unit tests.

Issues Fixed

Fixes: JetBrains/compose-multiplatform#2605

@m-sasha m-sasha requested a review from igordmn January 11, 2023 09:38
@m-sasha m-sasha changed the title Declare and implement a new v2.ScrollbarAdapter (variant 1) Declare and implement a new v2.ScrollbarAdapter Jan 11, 2023
@m-sasha
Copy link
Member Author

m-sasha commented Jan 11, 2023

I will probably also add tests (or a way to test) whether implementations of the old ScrollbarAdapter interface still work correctly.

@@ -128,9 +127,10 @@ fun defaultScrollbarStyle() = ScrollbarStyle(
* @param interactionSource [MutableInteractionSource] that will be used to dispatch
* [DragInteraction.Start] when this Scrollbar is being dragged.
*/
@Deprecated("Use androidx.compose.foundation.v2.VerticalScrollbar instead")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to add replaceWith, so IDEA can replace automatically.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried, but it doesn't work well. If I use

    replaceWith = ReplaceWith(
        expression = "androidx.compose.foundation.v2.VerticalScrollbar"
    )

or

    replaceWith = ReplaceWith(
        expression = "androidx.compose.foundation.v2.VerticalScrollbar(adapter, modifier, reverseLayout, style, interactionSource)"
    )

it breaks the code completely.

If I use

    replaceWith = ReplaceWith(
        expression = "VerticalScrollbar(adapter, modifier, reverseLayout, style, interactionSource)",
        "androidx.compose.foundation.v2.VerticalScrollbar"
    )

It updates the code "correctly" (only re-arranging the order of arguments), but it doesn't even add (much less replace) the import.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an idea. Nevertheless what we decide to do with JvmName approach - we don't create androidx.compose.foundation.v2.VerticalScrollbar, we just add overload androidx.compose.foundation.VerticalScrollbar(androidx.compose.foundation.v2.ScrollbarAdapter)

In this case, users can just replace rememberScrollbarAdapter

image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I we don't add JvmName's, I would describe the proper migration guide in rememberScrollbarAdapter message: Edit -> Find -> Replace in file -> replace "androidx.compose.foundation.rememberScrollbarAdapter" to "androidx.compose.foundation.v2.rememberScrollbarAdapter"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the latest code.

@@ -174,9 +174,10 @@ fun VerticalScrollbar(
* @param interactionSource [MutableInteractionSource] that will be used to dispatch
* [DragInteraction.Start] when this Scrollbar is being dragged.
*/
@Deprecated("Use androidx.compose.foundation.v2.HorizontalScrollbar instead")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, replace deprecated API in our internal example (example1/Main.jvm.kt)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need, after the changes to VerticalScrollbar and rememberScrollbarAdapter.

* limitations under the License.
*/

package androidx.compose.foundation.v2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an idea. We can use JvmName to not force users to change their code.

We can keep the old names and package, and still preserve the backward compatibility on binary level:

package androidx.compose.foundation

@JvmName("ScrollbarAdapter")
@Deprecated
interface ScrollbarAdapter1

@JvmName("ScrollbarAdapter2")
interface ScrollbarAdapter

We lose source compatibility though, but only in few cases, when users use custom implementation of ScrollbarAdapter. After they update version of Compose, they have to change interface to ScrollbarAdapter1, or provide viewportSize somehow. But it is better than forcing the majority of users who don't use custom adapters to replace deprecated calls.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use it for just rememberScrollbarAdapter/VerticalScrollbar, and create only v2.ScrollbarAdapter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -650,11 +695,12 @@ class ScrollbarTest {
}
}

@Suppress("DEPRECATION")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will probably also add tests (or a way to test) whether implementations of the old ScrollbarAdapter interface still work correctly.

It seems we still test the old functions here? How does thumb size on scrollbar smaller than viewport pass?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually using the new implementation behind the scenes.

ScrollbarAdapter(state) returns NewAdapterAsOld(NewScrollbarAdapter(scrollState)) and then when you pass it to Scrollbar, it just unwraps the new adapter:

private fun ScrollbarAdapter.asNewAdapter(trackSize: Int) =
    if (this is NewAdapterAsOld)
        this.newAdapter  // Just unwrap
    else
        OldAdapterAsNew(this, trackSize)

So existing code will automatically use the new implementation, and will enjoy the fix (unless it actually implements the old ScrollbarAdapter itself).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also means that I need to come up with a good way to test whether the old adapter really works when it's wrapped into OldAdapterAsNew.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice trick :).

Let's change code to the new API though, it is always better to test what users will use directly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now testing both new and old code.

@m-sasha m-sasha force-pushed the bugfix/new-scrollbar-adapter-v1 branch from 571728b to 19783c2 Compare January 14, 2023 10:35
@m-sasha
Copy link
Member Author

m-sasha commented Jan 16, 2023

Added testing of "old" ScrollbarAdapter implementations with the new code.

The only remaining untested code is NewScrollbarAdapterAsOld (not unwrapped), but

  1. It's very unlikely to be used anyway. Basically only by someone implementing their own scrollbar via our adapter.
  2. To test it we'd need to copy the entire old scrollbar implementation to the test.

@m-sasha m-sasha requested a review from igordmn January 17, 2023 10:51
/**
* Size of the viewport on the scrollable axis, or -1 if still unknown.
*/
internal var viewportSize: Int by mutableStateOf(-1, structuralEqualityPolicy())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 is more appropriate here, it won't mess with calculations that we can perform before the layout stage. We don't perform such calculations though (and shouldn't), so it is just a minor issue. If we would make it public, then it definitely should be not -1, it always hard to reason about magic constants.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with 0 at first, but then I thought that -1 would be better as it lets the developer know that it's not a known value yet. 0 is theoretically a legal value for the viewport size, so he wouldn't be able to tell between "unknown" and "known and is 0".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I noticed that I wasn't checking it correctly for -1, so I should.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to 0, as the Googler(s) also preferred it.

@m-sasha m-sasha merged commit 8c9299b into jb-main Jan 18, 2023
@m-sasha m-sasha deleted the bugfix/new-scrollbar-adapter-v1 branch January 18, 2023 14:29
igordmn pushed a commit that referenced this pull request Nov 15, 2023
…barAdapter (#368)

Declare and implement a new, v2.ScrollbarAdapter in order to distinguish between scrollbar (track) size and scrollable viewport size.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Scrollbar thumb is of wrong size when scrollbar (track) size is not the same as viewport size
2 participants