Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(CupertinoListTile): Click to wait. #161799

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

Closed

Conversation

StanleyCocos
Copy link
Contributor

@StanleyCocos StanleyCocos commented Jan 17, 2025

fix: #161790

Pre-launch Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I signed the [CLA].
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • I followed the [breaking change policy] and added [Data Driven Fixes] where supported.
  • All existing and new tests are passing.

@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: cupertino flutter/packages/flutter/cupertino repository labels Jan 17, 2025
@StanleyCocos StanleyCocos marked this pull request as draft January 17, 2025 10:10
@flutter-dashboard
Copy link

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@StanleyCocos StanleyCocos marked this pull request as ready for review January 17, 2025 13:42
Copy link
Contributor

@MitchellGoodwin MitchellGoodwin left a comment

Choose a reason for hiding this comment

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

Thank you for putting this together. I had some questions about the implementation and test.

@@ -161,12 +161,20 @@ void main() {
await tester.tap(find.byType(CupertinoListTile));
await tester.pump();

final TestGesture gesture = await tester.startGesture(
Copy link
Contributor

Choose a reason for hiding this comment

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

The combination of both this and the tap just above seems weird. Can you remove one, and still have the tests pass? Do you need this new gesture at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact, I am indeed a bit unsure about testing this change. Could you provide some advice on how I should test this modification?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the best thing to do is to add a new test entirely. Make a test with list tile that has an async callback, one that's long enough so that in the test you can pump it for an amount of time that's long enough for the background color to go back to the original color, but the callback isn't done yet. Theoretically that would fail before your change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the best approach would be to create a new test. However, since I modified the click behavior to refresh without waiting, the 'changes backgroundColor when tapped if onTap is provided' test would fail. Additionally, since the existing test can cover the scope of my changes, I didn’t create a new test but instead adjusted the existing one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Modifying this test to pass, and adding a new one would probably be the best approach then.

@@ -376,13 +376,12 @@ class _CupertinoListTileState extends State<CupertinoListTile> {
() => setState(() {
_tapped = false;
}),
onTapUp:
Copy link
Contributor

Choose a reason for hiding this comment

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

Since onTapUp is called just before onTap, would moving the setState to before the widget.onTap inside onTap work the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does achieve the same effect, but I think placing it in onTapUp seems more consistent with the action’s intent—refreshing the style when the finger is lifted.

@StanleyCocos
Copy link
Contributor Author

@MitchellGoodwin
Thank you for your review.
I’m not entirely sure if the delay during the click was intentional. However, from a user experience perspective, it is quite hard to accept.

@MitchellGoodwin
Copy link
Contributor

Can you post a screen recording of what this looks like when navigating to a new page? Part of the native design is that the tile keeps its darkened background while the page is transitioning out. I'm concerned that this implementation will remove that.

Screen.Recording.2025-01-27.at.11.35.07.AM.mov

@StanleyCocos
Copy link
Contributor Author

iOS Settings List

iOS.mp4

Before Fix

default.mp4

After Fix

default.mp4

@StanleyCocos
Copy link
Contributor Author

Because when clicking, the style refresh only occurs after the onTap function finishes execution. This raises an issue: if onTap involves a delayed function, it can result in the background remaining gray even after the user has lifted their finger. This could confuse users, making them think the app is lagging or unresponsive.

If the goal is to achieve a click effect similar to the iOS settings list, it might not be that simple to implement. It seems that even swipe actions are tied to this behavior. For now, I believe removing the wait is the most appropriate solution.

@MitchellGoodwin
Copy link
Contributor

Thank you for the videos. What I was worried about is not taking place. I think the proposed solution is fine, and further fine tuning is beyond the scope of this PR. All that's left is the test.

@StanleyCocos
Copy link
Contributor Author

I sincerely apologize. It seems I mistakenly recorded the wrong video. The correct version after the fix should be:

default.mp4

@StanleyCocos
Copy link
Contributor Author

Sorry, what exactly do you mean by the content outside the scope of this PR?

@MitchellGoodwin
Copy link
Contributor

I sincerely apologize. It seems I mistakenly recorded the wrong video. The correct version after the fix should be:

So it looks like the tap effect disappears mid transition, and is also not visible on page return. I don't think we want to regress on that behavior, as I think the visual effect is important to the design. It reduces visual "noise" on the page animating out, and on return draws the eye to where the user was previous interacting with the page.

Sorry, what exactly do you mean by the content outside the scope of this PR?

Apologies, I meant we don't have to get the tap effect perfect in this PR. Fixing the issue you listed is enough. That was in response to:

If the goal is to achieve a click effect similar to the iOS settings list, it might not be that simple to implement. It seems that even swipe actions are tied to this behavior. For now, I believe removing the wait is the most appropriate solution.

However if this fix causes other behavior to regress, then that is problematic.

@StanleyCocos
Copy link
Contributor Author

@MitchellGoodwin
Thank you for your guidance.

This request indeed changes some of the previous design effects. However, in my opinion, I believe this is actually a bug. When onTap is a time-consuming operation, ListTile may exhibit a sort of style lag, and developers cannot control this behavior. This can be quite problematic. Given that this would modify some of the previous design effects, do you think there are other ways to fix this issue? For example, adding a control parameter that allows developers to decide whether they need to wait for the time-consuming operation.

@MitchellGoodwin
Copy link
Contributor

For example, adding a control parameter that allows developers to decide whether they need to wait for the time-consuming operation.

In general, if a developer wants onTap to trigger an asynchronous function, but does not want to wait for the Future to resolve, than they can just pass a synchronous function to onTap that calls the Future. So I don't think an extra control parameter is necessary.

We want to cover cases where the developer is expecting a Future to resolve, but doesn't. The original issue was with launchUrl, which I'm unfamiliar with. Is the Future resolving when the widget is unmounted?

@StanleyCocos
Copy link
Contributor Author

StanleyCocos commented Feb 6, 2025

The initial issue was indeed caused by launchUrl, which occurred due to an incorrect callback.

return GestureDetector(
onTapDown:
(_) => setState(() {
_tapped = true;
}),
onTapCancel:
() => setState(() {
_tapped = false;
}),
onTap: () async {
await widget.onTap!();
if (mounted) {
setState(() {
_tapped = false;
});
}
},
behavior: HitTestBehavior.opaque,
child: child,
);

But the root cause is that the click on CupertinoListTile needs to wait for the onTap to complete before refreshing the style. However, due to potential time-consuming operations or interruptions like exceptions, it can result in the background remaining in the backgroundColorActivated state.

CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Settings'),
      ),
      child: ListView.separated(
        itemCount: 10,
        itemBuilder: (BuildContext context, int index) {
          return CupertinoListTile(
            title: Text('item $index'),
            onTap: () async {
              await Future.delayed(
                  const Duration(milliseconds: 2000), () {});
            },
          );
        },
        separatorBuilder: (BuildContext context, int index) => Padding(
          padding: EdgeInsets.symmetric(horizontal: 20.0),
          child: Divider(
            height: 1,
            thickness: 1,
            color: Colors.grey[300],
          ),
        ),
      ),
    )
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-02-06.at.18.34.27.mp4
Video code .
CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Settings'),
      ),
      child: ListView.separated(
        itemCount: 10,
        itemBuilder: (BuildContext context, int index) {
          return CupertinoListTile(
            title: Text('item $index'),
            onTap: () async {
              await Future.delayed(
                  const Duration(milliseconds: 2000), () {});
            },
          );
        },
        separatorBuilder: (BuildContext context, int index) => Padding(
          padding: EdgeInsets.symmetric(horizontal: 20.0),
          child: Divider(
            height: 1,
            thickness: 1,
            color: Colors.grey[300],
          ),
        ),
      ),
    )

The above is what I think is unreasonable. I’ve already finished clicking, and my finger has already left the screen. But the background is still backgroundColorActivated. This will inevitably happen in cases where there is a delay or abnormality in the click event. Of course, if the developer doesn’t use await in onTap, then this issue won’t occur.

Based on the above, although this PR can fix the issue, it also removes the previous design effects. Therefore, I’m not sure if it’s appropriate to continue with this PR at this stage.

@MitchellGoodwin
Copy link
Contributor

Based on the above, although this PR can fix the issue, it also removes the previous design effects. Therefore, I’m not sure if it’s appropriate to continue with this PR at this stage.

I don't think the current fix should be merged in, no. The tile should stay darkened while its onTap is unresolved. If a delay is expected, the onTap can be setup to handle that. If the delay is unexpected than that is either a bug that should be handled in the app code, or is an area we need to investigate.

I don't think worrying about delays manually added is worth fixing for, but the launchUrl situation should be looked into. If you have time to look into that and are willing, this PR can be left open and that can happen here. Otherwise we can close this PR for now.

@StanleyCocos
Copy link
Contributor Author

Hi, I actually think these are two separate issues. But since we’re discussing it here, I did some investigation yesterday. Specifically, I believe the issue occurs when using iOS’s SFSafariViewController. If the close button in the navigation bar is clicked before the page finishes loading, the completion callback isn’t called. This prevents the BasicMessageChannel from completing properly. It also causes the code after await launchUrl() to not execute, as it is still waiting for the callback.

@StanleyCocos
Copy link
Contributor Author

I submitted a PR, but testing seems to be challenging. I’m not sure how to simulate a user quickly closing it.

@MitchellGoodwin
Copy link
Contributor

I submitted a PR, but testing seems to be challenging. I’m not sure how to simulate a user quickly closing it.

Thank you for putting that PR together. Looks like Stuart responded on it, and he'll have much better feedback than I in that area.

I'm going to close this PR for now. I don't think a general change currently can be made on the framework side. It seems difficult for the list tile to make an assumption on how to handle a Future not correctly resolving, and should be done manually by the developer if they are using a callback that has that possibility.

@StanleyCocos
Copy link
Contributor Author

ok thank you for your guidance.

@StanleyCocos StanleyCocos deleted the fix/cupertino_list_tile branch February 10, 2025 05:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: cupertino flutter/packages/flutter/cupertino repository framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug Report: CupertinoListTile Remains backgroundColorActivated After launchUrl(url)
2 participants