App icon indicating copy to clipboard operation
App copied to clipboard

[HOLD for payment 2023-04-27] [$1000] The attachment viewer loads every image again when switching with arrows

Open kavimuru opened this issue 2 years ago • 50 comments

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Action Performed:

  1. Open any report with more than one image sent
  2. Click on any one image
  3. Click on an arrow to switch image

Expected Result

The spinner should only display once when the image loads, after that, the image should not need to show the spinner and be shown instantly

Actual Result

The spinner is shown on every image when switching and images are loaded again every time.

Workaround:

unknown

Platforms:

Which of our officially supported platforms is this issue occurring on?

  • [x] Android / native
  • [x] Android / Chrome
  • [x] iOS / native
  • [x] iOS / Safari
  • [x] MacOS / Chrome / Safari
  • [x] MacOS / Desktop

Version Number: 1.2.83-1 Reproducible in staging?: y Reproducible in production?: y If this was caught during regression testing, add the test name, ID and link from TestRail: Email or phone of affected tester (no customers): Logs: https://stackoverflow.com/c/expensify/questions/4856 Notes/Photos/Videos:

https://user-images.githubusercontent.com/43996225/224829096-7835d17f-58a9-4d71-b04b-db2b30a79cdd.mov

https://user-images.githubusercontent.com/43996225/224829210-cfb2f08f-2543-4d2e-bcc3-66e5f90da9d6.mp4

Expensify/Expensify Issue URL: Issue reported by: @esh-g Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1678613459293019 View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~0136002576e7090b5b
  • Upwork Job ID: 1636029895829024768
  • Last Price Increase: 2023-03-15

kavimuru avatar Mar 13 '23 20:03 kavimuru

Triggered auto assignment to @isabelastisser (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details.

MelvinBot avatar Mar 13 '23 20:03 MelvinBot

Bug0 Triage Checklist (Main S/O)

  • [x] This "bug" occurs on a supported platform (ensure Platforms in OP are ✅)
  • [x] This bug is not a duplicate report (check E/App issues and #expensify-bugs)
    • If it is, comment with a link to the original report, close the issue and add any novel details to the original issue instead
  • [x] This bug is reproducible using the reproduction steps in the OP. S/O
    • If the reproduction steps are clear and you're unable to reproduce the bug, check with the reporter and QA first, then close the issue.
    • If the reproduction steps aren't clear and you determine the correct steps, please update the OP.
  • [x] This issue is filled out as thoroughly and clearly as possible
    • Pay special attention to the title, results, platforms where the bug occurs, and if the bug happens on staging/production.
  • [x] I have reviewed and subscribed to the linked Slack conversation to ensure Slack/Github stay in sync

MelvinBot avatar Mar 13 '23 20:03 MelvinBot

Job added to Upwork: https://www.upwork.com/jobs/~0136002576e7090b5b

MelvinBot avatar Mar 15 '23 15:03 MelvinBot

Current assignee @isabelastisser is eligible for the External assigner, not assigning anyone new.

MelvinBot avatar Mar 15 '23 15:03 MelvinBot

Triggered auto assignment to Contributor-plus team member for initial proposal review - @thesahindia (External)

MelvinBot avatar Mar 15 '23 15:03 MelvinBot

Triggered auto assignment to @johnmlee101 (External), see https://stackoverflow.com/c/expensify/questions/7972 for more details.

MelvinBot avatar Mar 15 '23 15:03 MelvinBot

Hey @isabelastisser do you mind completing the checklist https://github.com/Expensify/App/issues/15922#issuecomment-1466937899

johnmlee101 avatar Mar 15 '23 15:03 johnmlee101

sorry, forgot to check the boxes before applying the external label!

isabelastisser avatar Mar 15 '23 15:03 isabelastisser

I think this is a dupe of https://github.com/Expensify/App/issues/6527 which was deferred for https://github.com/Expensify/App/issues/12603 which ultimately deals with caching these images

johnmlee101 avatar Mar 15 '23 15:03 johnmlee101

@johnmlee101 Are you sure this is a dupe of #6527 ? Because I think that issue was even before the carousel control was implemented and was about caching the images to the browser so they stay even after reload. But here, the images are already loaded and we can have them switch without caching them to the browser.

esh-g avatar Mar 15 '23 17:03 esh-g

Proposal

Please re-state the problem that we are trying to solve in this issue.

The spinner keeps shown on every image when switching even it loaded before.

What is the root cause of that problem?

Because in the componentDidUpdate lifecycle of component ImageView, it call method to set isLoading=true everytime the url of is changed (it didn't care if the url is passed before or the image is already loaded in cache) https://github.com/Expensify/App/blob/eedd92a2547a18dc60a810c9f868bf5b3ba77681/src/components/ImageView/index.js#L63-L69

when isLoading state equal true, it shows spinner https://github.com/Expensify/App/blob/eedd92a2547a18dc60a810c9f868bf5b3ba77681/src/components/ImageView/index.js#L298-L302

What changes do you think we should make in order to solve the problem?

I think we should apply delayed spinner pattern in this case. According to some UX tips, we should only show spinner if something is loaded > 1s, otherwise it will distract user.

More details, I think about add an additional props delayTime to component FullScreenLoadingIndicator, and update current FullScreenLoadingIndicator to something like this:

import React, { useEffect, useState } from 'react';

const defaultProps = {
    ....
    delayTime: 0,
};

const FullScreenLoadingIndicator = (props) => {
  const [showable, setShowable] = useState(false);

  useEffect(() => {
    const timer = setTimeout(() => setShowable(true), props.delayTime);

    return () => clearTimeout(timer);
  });

  return showable && <..../>;
};

export default FullScreenLoadingIndicator;

so we can set delayTime in the loading part of component ImageView. The advantage of this pattern is we also improve UX for user when they load lightweight images or their network is very good

What alternative solutions did you explore? (Optional)

Maybe cache loaded images, but it will be a fancy work.

hoangzinh avatar Mar 15 '23 17:03 hoangzinh

Proposal

I think you need to render each AttachmentView for every attachment in AttachmentCarousel and use style display: none;. Because your AttachmentView will re-render every time when current slide will be change.

You can cache image content or make Set with loaded urls, but i think it more complicated and redundant for Image component.

Plus, if you will use style, you can use opacity and it is good for animation.

Contributor details Your Expensify account email: [email protected] Upwork Profile Link: https://www.upwork.com/freelancers/~01146d8467ba0b62e4

nefedov-dm avatar Mar 15 '23 19:03 nefedov-dm

📣 @nefedov-dm! 📣

Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork. Please follow these steps:

  1. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
  2. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
  3. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.

Screen Shot 2022-11-16 at 4 42 54 PM

Format:

Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>

MelvinBot avatar Mar 15 '23 19:03 MelvinBot

Proposal

Please re-state the problem that we are trying to solve in this issue.

The attachment viewer loads every image again when switching with arrows

What is the root cause of that problem?

The Image component used debouncedConfigureImageSource with 220 ms: https://github.com/Expensify/App/blob/main/src/components/Image/index.js#L14

this.debouncedConfigureImageSource = _.debounce(this.configureImageSource, 220);
...
this.debouncedConfigureImageSource();
...
this.debouncedConfigureImageSource.cancel();
this.debouncedConfigureImageSource();

What changes do you think we should make in order to solve the problem?

It will be fixed if we use it with the immediate: true option: this.debouncedConfigureImageSource = _.debounce(this.configureImageSource, 220, true);

Or if we use it without debounce.

It needs more investigation like why the debounce actually used to make sure that the fix won't break anything.

What alternative solutions did you explore? (Optional)

usenbekov avatar Mar 16 '23 00:03 usenbekov

Proposal

Updated

Updated according to contributing guidelines.

usenbekov avatar Mar 17 '23 10:03 usenbekov

This is expected behavior, debounce here is added to ensure there is no flickering of the image while switching images fast.

cc: @chiragsalian @luacmartins @trjExpensify @thesahindia

Santhosh-Sellavel avatar Mar 17 '23 19:03 Santhosh-Sellavel

Not overdue, still discussing!

@johnmlee101 is this a dupe or expected behavior? Let me know if I should close it.

isabelastisser avatar Mar 17 '23 19:03 isabelastisser

This is expected behavior, debounce here is added to ensure there is no flickering of the image while switching images fast.

cc: @chiragsalian @luacmartins @trjExpensify @thesahindia

Image component mounted -> after 220ms -> configureImageSource -> RNImage.getSize -> onLoad -> isLoading: false

So even if the image is getting from the cache it waits for 220ms and then calls onLoad. If I'm not missing anything then I think it shouldn't be like that.

usenbekov avatar Mar 17 '23 20:03 usenbekov

@usenbekov That's right, but it should be like that, and debounce duration is added to avoid unnecessary flickering while switching between images faster.

Moreover, we are planning to improve the Image to remove debounce for some cases and that's in planning we will get started on that next week.

@chiragsalian or @luacmartins Can you comment on this thanks!

cc: @thesahindia @johnmlee101

Santhosh-Sellavel avatar Mar 17 '23 20:03 Santhosh-Sellavel

Hey guys I think my PR here, improves the situation a bit

  • https://github.com/Expensify/App/pull/15663

It fixes a workaround we had with image flickering for avatars, by fixing react-native-web bug where internal image cache was ignored (delaying the displaying of an already loaded image a bit)

The PR also makes the debounce here unnecessary - we no longer have a flicker between prev/next image https://github.com/Expensify/App/blob/0b24114bfd99f92e24f849e46a7ed2622adbee7c/src/components/Image/index.js#L14

Check this out:

https://user-images.githubusercontent.com/12156624/226054739-1c464e09-efb4-4a92-8064-737c5cb63f70.mov

Yeah, the spinner still shows briefly, so I like the idea of waiting a bit before rendering it (~BTW this is DEV, so it's possible the spinner would not show at all on a PROD build~: still appears on PROD)

kidroca avatar Mar 17 '23 21:03 kidroca

The Carousel strategy might be improved a bit if we include image preloading (This would also fix the current problem with showing the spinner for cached images)

  1. When we open an attachment / carousel (modal)
  2. An image is starts loading (we see a spinner then the image)
  3. After the image loads we Image.preload the image before/after (e.g. the ones that would be shown when we press an arrow)
  4. Since Image.preload returns a promise we can track whether it's still loading or not (images from cache would preload instantly)
  5. When the actual arrow is pressed - we only show loading if the image is still (pre)loading - otherwise we don't show a spinner but directly display the image

kidroca avatar Mar 17 '23 21:03 kidroca

@kidroca I couldn't find Image.preload method in RN docs, is it your custom method?

hoangzinh avatar Mar 18 '23 00:03 hoangzinh

Thanks guys! Let's hold this issue.

cc: @johnmlee101

thesahindia avatar Mar 18 '23 05:03 thesahindia

@kidroca I couldn't find Image.preload method in RN docs, is it your custom method?

Sorry the name is actually prefetch, it's this one https://reactnative.dev/docs/image#prefetch

kidroca avatar Mar 18 '23 11:03 kidroca

@kidroca yay, I'm not really sure how you will implement it, but caching is one of hardest problems. If we're going to implement an array or something to mark fetched status of an image url, it would take effort. I prefer let this hardest thing to the native system handle it. The delay spinner is good enough for our case.

hoangzinh avatar Mar 18 '23 14:03 hoangzinh

@Santhosh-Sellavel WRT this, yes we plan to improve the Image component by removing the debounce. Not sure if that will get rid of the loading issue though.

luacmartins avatar Mar 20 '23 14:03 luacmartins

Yes, I agree!

Santhosh-Sellavel avatar Mar 20 '23 15:03 Santhosh-Sellavel

@Santhosh-Sellavel so will this be resolved in that issue?

johnmlee101 avatar Mar 20 '23 15:03 johnmlee101

It's best to hold until #15663 is merged

Santhosh-Sellavel avatar Mar 20 '23 17:03 Santhosh-Sellavel

Not overdue, still on hold!

isabelastisser avatar Mar 22 '23 18:03 isabelastisser