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

Update SelectableLazyColumn for key separation and update Gradle wrapper #216

Closed
wants to merge 4 commits into from

Conversation

lamba92
Copy link
Collaborator

@lamba92 lamba92 commented Oct 30, 2023

Refactor SelectableLazyColumn and related classes, separating selectionKey and uiKey. selectionKey is used for selection and uiKey triggers recompositions. Also, upgraded Gradle wrapper to 8.4. This change improves re-composition handling and provides an easier way to control item selection without unnecessary recompositions.

Refactor SelectableLazyList and related classes, separating `selectionKey` and `uiKey`. `selectionKey` is used for selection and `uiKey` triggers recompositions. Also, upgraded Gradle wrapper to 8.4. This change improves re-composition handling and provides an easier way to control item selection without unnecessary recompositions.
@lamba92 lamba92 requested review from rock3r and fscarponi October 30, 2023 17:33
@lamba92 lamba92 self-assigned this Oct 30, 2023
gradle/wrapper/gradle-wrapper.properties Outdated Show resolved Hide resolved
samples/standalone/build.gradle.kts Outdated Show resolved Hide resolved
Comment on lines +62 to +68
LaunchedEffect(Unit) {
while (true) {
delay(1.seconds)
listOfItems = listOfItems.mapIndexed { index, s ->
if (index != 2) s else "WHOOO ${Random.nextInt(0, 10)}"
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this? If it's test code — as it looks to be — please revert. If not please explain why you need it to be in the sample.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is a test for the changes. The objective of this PR is to allow to recompose when an item changes without loosing the selection. In other words, the selection should be on a subset of the data available in the item.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah it's not something I would include in the sample for everyone though, especially since it's not something we want anyone to actually be doing :P

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please only keep this change locally while you develop.

items(
count = listOfItems.size,
selectionKey = { index -> index },
uiKey = { index -> listOfItems[index] },
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the end goal is to use the item itself as data key for the recomposition, why don't we just do that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The objective of this PR is to allow to recompose when an item changes without loosing the selection. In other words, the selection should be on a subset of the data available in the item.

Copy link
Collaborator

@rock3r rock3r Oct 31, 2023

Choose a reason for hiding this comment

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

the selection should be on a subset of the data available in the item

And yet here the selection key is not based on the item data 🤔 but the UI key is

settings.gradle.kts Outdated Show resolved Hide resolved
Copy link
Collaborator

@Walingar Walingar left a comment

Choose a reason for hiding this comment

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

I don't see a reason for this change, so for now I am against of providing complicated logic

.onFocusChanged {
isFocused = it.isFocused || it.hasFocus
}
.focusGroup()
Copy link
Collaborator

Choose a reason for hiding this comment

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

There shouldn't be any focusable elements inside SelectableLazyColumn, otherwise tab behavior will work incorrectly, so we need to check only isFocused state and don't make it focusGroup

Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably I'm missing something here, the only selectable element is the LazyColumn (due SEL is a wrapper).
if you are going to remove focusable from the LC, then you will have no way to send keybindings event to the component.
BTW in most use cases, I doubt that users will not put something clickable inside the SLC, and if I'm not mind wrong, we cannot prevent that
Probably we can accept that and find a way to handle TAB key-event properly by moving in some way the focus to the next element

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think I agree with @Walingar. On the other hand we need to warn the user to not use focusable or clickable modifiers on items inside the list. A solution could be to add such modifiers with all their overloads as extension members of SelectableLazyListScope and mark such modifiers as errors using annotations.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't know how that scales. I reckon a Lint check would the right way to go, but since we don't have that, a Detekt check would be good as well.

contentType: (index: Int) -> Any? = { null },
selectable: (index: Int) -> Boolean = { true },
itemContent: @Composable SelectableLazyItemScope.(index: Int) -> Unit,
)

/**
* A method that enables sticky header behavior in a list or grid view.
* By default `[selectionKey] = [uiKey]`, but you can assign [uiKey] if you need to
Copy link
Collaborator

Choose a reason for hiding this comment

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

But if we keep only uiKey as it was before, then with unchanged uiKey element can be recomposed if State is changed.
Key is used mostly for keeping scroll in a proper position on items modification, not for skiping recomposition in this case

Can you please provide a usecase where you need different uiKey and selectionKey? Since example for Chips and Tree is wrong, since you need to provide not string but objects that hold State if you want to change it.

Copy link
Collaborator

@fscarponi fscarponi Oct 31, 2023

Choose a reason for hiding this comment

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

Can you please provide an usecase where you need different uiKey and selectionKey

I can try to give you a reason: imagine you have a not so small item in the dataset,

  • probably you will handle the item key with a minimal key (as example, you can use a UUID as key)
  • now something inside this item will change (or the whole item too)
  • if you have the same key for both, your item will not be shown in the list; otherwise, you lose the selection because the key is not the same

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, I still don't get it. Let's just use selectionId as itemId then? It's the same.

you lose the selection because the key is not the same

And this is expected behaviour, item is not the same, it shouldn't be selected. For sure, it depends on requirements, but this should be handled by SelectableLazyColumn itself then. I mean how to handle selection when selected items are removed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is needed in case you want to decouple the selection from recompositions. Indeed having a state inside the item to pass would be optimal, but in the case of PKGS it is not possible. We have a huge complex model and all the data to construct the tree is not reactive (flows/states ecc...). Everytime there is a change in a single part of the model, the entirety of the model gets re-emitted.

We could transform a portion of the model from plain properties to reactive components but that would require a huge rewrite and would also mean a lot of issues with the IJ platform (not everything is easily transformed into a reactive component).

Copy link
Collaborator

Choose a reason for hiding this comment

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

So then you need to introduce ids for your items and use them as keys

Copy link
Collaborator

Choose a reason for hiding this comment

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

Otherwise, scrolling will be broken on recomposition also.

@lamba92 lamba92 closed this Oct 31, 2023
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.

4 participants