expo: [expo-image] `recyclingKey` breaks Blurhash placeholder size (`FlashList`)

Minimal reproducible example

https://github.com/JacobJaffe/reproduction-expo-image-blurhash-recycler

Summary

Hi, I’m working on optimizing a very long (thousands) list of ExpoImage by migrating to FlashList. It appears that when a recycled view occurs, with reclyingKey, the placeholder does not get properly resized (for a Blurhash).

This issue only affects iOS.

What I’m seeing is that the first screen’s worth of blurhash’s properly fill the image space, and then recycled images have the placeholder occupy a very small amount of space (see image).

I’m using 32x32 Blurhashes here, which isn’t quite the size the placeholder is drawing to, but is similar.

233483959-e6a172dc-caf9-44ad-b4e5-7662132c7406

Environment

expo-env-info 1.0.5 environment info: System: OS: macOS 13.2.1 Shell: 5.8.1 - /bin/zsh Binaries: Node: 14.20.0 - ~/.nvm/versions/node/v14.20.0/bin/node Yarn: 1.22.19 - /opt/homebrew/bin/yarn npm: 6.14.17 - ~/.nvm/versions/node/v14.20.0/bin/npm Watchman: 2023.02.27.00 - /opt/homebrew/bin/watchman Managers: CocoaPods: 1.12.0 - /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: DriverKit 22.2, iOS 16.2, macOS 13.1, tvOS 16.1, watchOS 9.1 IDEs: Android Studio: 2021.2 AI-212.5712.43.2112.8815526 Xcode: 14.2/14C18 - /usr/bin/xcodebuild npmPackages: expo: ^48.0.0 => 48.0.5 react: 18.2.0 => 18.2.0 react-dom: 18.2.0 => 18.2.0 react-native: 0.71.3 => 0.71.3 react-native-web: ~0.18.11 => 0.18.12 npmGlobalPackages: eas-cli: 3.9.3 Expo Workflow: managed

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 1
  • Comments: 16 (13 by maintainers)

Most upvoted comments

Have you tried using: placeholderContentFit="cover"

That’s what I used to fix the placeholder sizing, however, I have another issue with expo-image: https://github.com/expo/expo/issues/22515

Have you tried using: placeholderContentFit="cover"

That’s what I used to fix the placeholder sizing, however, I have another issue with expo-image: #22515

🙌🏻 Thank you, this fixes the issue without any work arounds

That statement is simply false. Using FlatList is always the wrong choice because it consumes at least 6 times more CPU time than FlashList. While disabling recycling for an image might not turn your FlashList into a FlatList (performance is still better), it’s best to avoid it. I’m raising my hand here to prevent the spread of incorrect information or bad advice that others might use.

Never use FlatList, and avoid using keys if possible. In rare cases like yours, it might be a feasible workaround as long as it’s only set on the image and nowhere else.

Please don’t do conditional Lists 😃.

It is even recommended to use FlashList instead of ScrollView, as this helps a lot with mount times due to its lazy behavior. Shopify will release a LazyScrollView which make this easy in the future as well (instead of abusing renderItem)

and is mostly a simple wrapper for FlatList on iOS

And this is also another false statement. FlashList uses recyclerListView for both iOS and Android + does some heavy layout calculations in native land for better performance. FlatList recreates all elements, while FlashList reuses (recycles) them.

Conditional platform specific code is completely acceptable. You can do this at a variety of levels depending on the abstraction you need, from an in-line check to .ios / .android bundle-level.

You’re also wrong that we shouldn’t do conditional lists, because that’s a fundamental feature of React’s composability.

Sorry, this one was a misunderstanding. I wasn’t talking about conditional rendering in general (we use it all the time), but rather suggesting not to use FlatList for iOS and FlashList for Android unless FlatList provides an advantage that justifies its poor performance and high CPU usage. (maintainVisibleContentPosition for example, but this is coming soon)

If you encounter unsolvable issues with FlashList, then using FlatList is a viable option. However, you should consider the performance differences between the two before making a decision.

As someone who has created hundreds of lists, struggled with FlatList for years, and used every optimization technique available, I can only conclude that it is best not to use FlatList. I personally haven’t had a case where I couldn’t migrate away from it, with big benefits.

Regarding CPU time, there is almost a 6x difference between FlatList and FlashList. Battery consumption on large lists with FlatList is significant, even on iOS. It is not advisable to recommend FlatList just because iOS handles it better than Android, as battery life is also an important consideration. We conducted plenty of measurements, and Bamlab (who created some Flipper measurement plugins in a LightHouse score style) conducted a comprehensive series of measurements, and FlashList outperformed FlatList in every aspect, including memory usage, mount time, battery consumption, frame drops, and CPU time.

Anyone who says that we ‘never use’ something can’t really be taken seriously as it’s never that simple.

Love how folks always start to get personal in a discussion.

but it’s a dangerous mindset to rely on something so unconditionally because of hype or what-not

Definitely not following any hype. But do you do and enjoy FlatList my friend!

That statement is simply false. Using FlatList is always the wrong choice because it consumes at least 6 times more CPU time than FlashList. While disabling recycling for an image might not turn your FlashList into a FlatList (performance is still better), it’s best to avoid it. I’m raising my hand here to prevent the spread of incorrect information or bad advice that others might use.

Never use FlatList, and avoid using keys if possible. In rare cases like yours, it might be a feasible workaround as long as it’s only set on the image and nowhere else.

Please don’t do conditional Lists 😃.

It is even recommended to use FlashList instead of ScrollView, as this helps a lot with mount times due to its lazy behavior. Shopify will release a LazyScrollView which make this easy in the future as well (instead of abusing renderItem)

@hirbod I have to agree with @JacobJaffe, the performance gains you quoted on FlashList applies mostly to Android because it uses RecyclerView, and is mostly a simple wrapper for FlatList on iOS, so it is perfectly fine to use FlashList only on Android.

You’re also wrong that we shouldn’t do conditional lists, because that’s a fundamental feature of React’s composability. A device’s platform doesn’t change throughout the lifetime of the app, for obvious reasons, so as long as you make sure that the calculation for the container list component only happens once, there is virtually no performance degradation for using conditional lists.

Anyone who says that we ‘never use’ something can’t really be taken seriously as it’s never that simple. For example, I’ve been noticing issues with FlashList on horizontal scroll. Sure, they may iron this out in the future, but it’s a dangerous mindset to rely on something so unconditionally because of hype or what-not.