Closed jellynoone closed 5 months ago
I could clamp the index to total number of items in case that changes to get around the assertion. Other than I'm not sure what else to do given that SliverChildDelegate
which ultimately backs the children does not have a concept of identity for items and as such can't really detect item removal.
I think clamping would be a great start that would cover most code paths.
As for item synchronisation, personally, I think it would rarely happen.
However, a possible solution that comes to mind, (although I haven't checked the internals in detail so I might be wrong here) would be to get the render object of the item being scrolled to and reuse it on further calls? Something, like:
class ExtentManager {
double? getOffsetToReveal(RenderObject item);
(double, RenderObject) getInitialItemToRevealData(int index);
}
The RenderObject
is not available initially. It is created on demand when it reaches the cache area during scrolling.
Thank you for the explanation.
Could another potential solution be to ask the caller about the current index on each animation tick? For instance, instead of
abstract interface class ListController {
void animateToItem({
required int index,
required ScrollController scrollController,
required double alignment,
required Duration Function(double estimatedDistance) duration,
required Curve Function(double estimatedDistance) curve,
Rect? rect,
});
}
we'd have:
abstract interface class ListController {
void animateToItem({
required int? Function() getItemIndex,
required ScrollController scrollController,
required double alignment,
required Duration Function(double estimatedDistance) duration,
required Curve Function(double estimatedDistance) curve,
Rect? rect,
});
}
Similar to what we have with duration and curve?
If the getItemIndex
call returned null
, it would indicate the scroll should stop perhaps reusing the last target offset and finishing animating to it?
I like this! I'm not sure if this should be a breaking change or a separate method (if so it needs different name). I think when returning null
I'd just stop the animation.
I can see a use case for both of these methods, for instance, if you're building a chat application and want to redirect the user to a specific message, you'd use the index getter variant. Whereas if you are building a news application and want to navigate the user to the 10th most popular article, you'd always use the index directly.
A potential solution I have in mind would be something like the following (additional parameters skipped for brevity):
abstract interface class ListController {
/// Directly jumps to the selected index.
/// The caller is responsible for providing a valid index.
void jumpToItem(int index);
/// Animates to the selected index.
/// The animation manager validates the index continues to
/// be valid during the entire duration of the animation. If it
/// stops being valid, the animation ends.
void animateToItem({
required int index,
});
/// Animates to the selected live index.
/// The caller is responsible for keeping the index valid for
/// the duration of the animation.
void animateToLiveItem({
required int? Function() index,
});
}
At first, I wanted to suggest breaking animateToItem
and adding animateToIndex
that could potentially just forward the call to animateToItem
. However, since we have jumpToItem
already and it doesn't make sense for it to come in two variants (because the caller will always have to provide a valid index and only has to do it once). I think its best to keep the current naming and signature for these methods.
Additionally, if animateToIndex
simply forwarded a call to the animateToItem
variant, the developer could still hit the errors we are trying to prevent here. Which made me think the proposed solution would be best.
@knopp What do you think?
@knopp Were you able to give this proposal a thought?
I'd simply go with changing the animateToItem
signature to replace item index with a ValueGetter
.
void animateToItem({
required ValueGetter<int> index,
required ScrollController scrollController,
required double alignment,
required Duration Function(double estimatedDistance) duration,
required Curve Function(double estimatedDistance) curve,
Rect? rect,
})
This would be a breaking change but it's rather minor and I think it's better than cluttering API.
@knopp Thank you for the fix! But how about the case when the item is fully removed and there is no backing index to return?
Right. I think the return value should be nullable (null cancelling the animation).
Consider the following example:
When pressing on the floating button, an assertion error will be issued that the index does not exist, even though at the start of the animation it does.
It seems when performing animations, the simulation expects the final index to be present during the entire animation. While this example is extreme in that removing of the item happens immediately after the animation start.
In addition, looking at: https://github.com/superlistapp/super_sliver_list/blob/a4a4326d4cc89207e9e5079d6e7b39c9aab4993c/lib/src/animate_to_item.dart#L45-L52
It seems
index
is always used to determine the next position to use. Couldn't this also lead to cases where the items change in such a way that the index would be pointing to a different item not the one originally intended?