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

Adding support for LazyColumn (FlatList) #9

Closed
fabOnReact opened this issue Nov 18, 2024 · 9 comments
Closed

Adding support for LazyColumn (FlatList) #9

fabOnReact opened this issue Nov 18, 2024 · 9 comments

Comments

@fabOnReact
Copy link

fabOnReact commented Nov 18, 2024

Related Issue fabOnReact/react-native-wear-connectivity#12

What are the limitations of using LazyColumn component instead of traditional ScollView?
What are the limitations of using Jetpack Compose Components with React Native?

Searching ShikaSD in react-native-community/discussions-and-proposals#446 brings up some interesting info.

I would like to add support for ScalingLaxyColumn in the library as it is important for WearOS development with react-native-wear-connectivity.

cc @gaurav21r also interested in helping with this

I'm asking you, as you probably investigated limitations and maybe found workarounds around them. Thanks a lot

@andrew-levy
Copy link
Owner

@fabOnReact Hello! I am no expert, but Column doesn't scroll by default. I think you need LazyColumn for that to scroll. I tried implementing it but it was giving me errors so I stopped for now. If you can get it working that would be great!

As for limitations of compose in RN, it's just very untested. There are some kinks we need to work out and it doesn't always work how you'd expect. I'm hoping to solve most of the issues in this library though.

@fabOnReact fabOnReact changed the title Adding support for ScalingLaxyColumn Adding support for ScalingLazyColumn Nov 21, 2024
@fabOnReact
Copy link
Author

fabOnReact commented Nov 21, 2024

fabOnReact/react-native-wear-connectivity#12 (comment)

my problem with ScalingLazyColumn was rendering a ViewGroup, like done with FlatList renderItem and data.

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt;l=44?q=LazyColumn

I believe there is a difference in implementation between LazyColumn and FlatList.

LazyColumn maybe is a similar implementation to RecyclerView. The problem is that with our React Native architecture handles in JavaScript the virtualization (mounting and unmounting of items in a list), which may not be compatible with this APIs.

The RecyclerView adds/removes rows while scrolling the content. As done with the FlatList component, the renderItem prop defines the Layout for each row and the data prop the information to populate in each row.
React Native FlatList implementation calculates these rows in JavaScript with the VirtualizedList. The rows are added/removed in VirtualizedList JS implementation and sent to Android API as children of the ScrollView.js component. ReactScrollView is a ViewGroup and adds the views with addViews.

4h2wjwo0dlzkenw9ewnm

There is an implement of RecyclerView (react-native-recyclerview-list), but the library has some limitations.

There are some issues with setState which does not update the UI. The updates need to be triggered manually.
https://github.com/godness84/react-native-recyclerview-list/blob/864021c01ef42b1a451c526c8f65a30ff33c2d2a/android/src/main/java/com/github/godness84/RNRecyclerViewList/RecyclerViewBackedScrollView.java#L191-L200

I don't believe there is an easy way to do this. Many big companies like Shopify, Flipkart did not try to use RecyclerView, instead they followed the FlatList implementation which leverages ScrollView native API.

You may be able to render the Jetpack Compose items (like Text) in a traditional ScrollView.

@fabOnReact fabOnReact changed the title Adding support for ScalingLazyColumn Adding support for LazyColumn Nov 21, 2024
@fabOnReact fabOnReact changed the title Adding support for LazyColumn Adding support for LazyColumn (FlatList) Nov 21, 2024
@fabOnReact
Copy link
Author

fabOnReact commented Dec 19, 2024

there is a way to implement this, building a new API based on WearableRecyclerView or RecyclerView or LazyColumn:
1 - instead of using FlatList renderItem, register the renderItem on the native side using Function::createFromHostFunction
2 - implement custom API to handle setState and update props.

Using JSI may mean skipping renderer, that is why there is no support for setState or update props for components rendered in the renderItem function registered on native side with createFromHostFunction

https://github.com/facebook/react/tree/main/packages/react-reconciler

also I don't think createFromHostFunction supports JSX, because the renderer is responsible for creating native views like TextView from JSX views like Text

probably is similar implementation to the one used in https://github.com/godness84/react-native-recyclerview-list/blob/864021c01ef42b1a451c526c8f65a30ff33c2d2a/android/src/main/java/com/github/godness84/RNRecyclerViewList/RecyclerViewBackedScrollView.java#L191-L200

react-native-recycler-view uses ViewGroup addView

@fabOnReact
Copy link
Author

You’ve outlined a theoretically possible approach—essentially bypassing React Native’s usual rendering and reconciliation pipeline by registering a native-side renderItem function using the JSI (via something like Function::createFromHostFunction). This would let you feed data and trigger UI updates directly from native code, potentially driving a Compose list component without going through the normal React Native “VirtualizedList → ScrollView → native views” pipeline.

However, this approach comes with significant challenges and limitations:

  1. Skipping the Renderer Means Losing the Declarative UI Advantages

React Native’s architecture relies on the React renderer (reconciler) to interpret JSX and update the native UI tree accordingly. Without it, you are stepping out of the standard React Native lifecycle:
• No Automatic Update Propagation:
Normally, changing props or state in React triggers a re-render in JS, and the reconciler updates the native views accordingly. If you manually register a renderItem on the native side via createFromHostFunction, you lose this default behavior. You’d need a custom mechanism to trigger updates and recompose items.
• No JSX Support:
JSX is not just syntax sugar for function calls—it’s deeply integrated with the React reconciliation pipeline. Without the reconciler, you have no out-of-the-box way to translate components into native text views or Compose-based equivalents. You must manually handle the creation and update of each native/Compose UI element. Essentially, you revert to an imperative model and lose the declarative nature of React.

  1. Managing State and Props Manually

You mentioned implementing a custom API to handle setState and updating props. In normal React Native, setState on the JS side triggers a reconciliation pass that updates native views. By sidestepping this and implementing your own “state management,” you assume responsibility for:
• State Synchronization:
You’d have to define when and how to notify the native side of prop changes. Without the reconciler, you need a custom subscription or callback system to ensure that whenever state changes in JS, the native code knows to re-render the items.
• Granular Updates:
If data changes only for one item in a LazyColumn, the React reconciler would typically update just that item. You must now replicate that logic by identifying which items changed and re-issuing the native renderItem call with updated data. This could lead to performance issues if not handled efficiently.

  1. Complexity of the JSI Approach

Function::createFromHostFunction allows you to create a JSI function that JS can call, but integrating this neatly into a React Native app is non-trivial:
• Bridging Logic:
You’ll be writing native modules that receive JS calls, produce composable UI in Compose, and somehow return results or updates. Maintaining this bridging code could be complex, error-prone, and poorly documented.
• Reinventing the Wheel:
Essentially, you’d be building a custom mini-renderer or a bridge that mimics the job of the React Native reconciler for your specific list scenario. This is a large undertaking and must be carefully designed to maintain performance and correctness.

  1. Similarities to Existing Attempts (e.g., react-native-recyclerview-list)

The react-native-recyclerview-list library attempted a similar low-level integration by using RecyclerView. The issues encountered there—like having to manually trigger updates because setState wouldn’t automatically re-render items—illustrate the kind of complexities you’ll face.

React Native’s architecture is optimized around the idea that the JavaScript side controls the layout and updates declaratively, while the native layer provides a stable set of host components (like ScrollView, Text, View). Once you step outside that contract and try to integrate a radically different approach (Compose’s LazyColumn or ScalingLazyColumn) via JSI host functions, you no longer benefit from that architecture.

  1. Maintenance and Future-Proofing

As React Native evolves (particularly with the Fabric renderer and TurboModules), you may find that maintaining a hand-rolled integration becomes even more difficult. Future changes in the way React Native’s reconciler or JSI integration work may break your carefully crafted solution. Without a stable API designed for this use case, you’ll be doing a lot of maintenance.

In conclusion, while your outlined approach is theoretically possible, it’s akin to building a custom, partial React Native renderer on the native side for Compose-based lists. You’d be forgoing the standard reconciler, losing JSX and declarative updates, and taking on manual state and update management. This is a high-complexity, low-leverage solution. Unless you have a very specific reason and the resources to maintain this custom integration layer, it’s generally easier and more sustainable to stick within the supported paradigms—using ScrollView and FlatList, or creating a very specialized native module that at least mimics how the reconciler and Fabric renderer handle updates.

@andrew-levy
Copy link
Owner

Hey! This is a lot of research thanks. I'm not sure I'm following the issue tho. What exactly is the problem you're trying to solve? The goal of this library is to simply expose compose views to RN @fabOnReact

@fabOnReact
Copy link
Author

fabOnReact commented Dec 21, 2024

I understand now your point. LazyColumn for you is not an essential component because you can use FlatList or ScrollView with Jetpack Compose views (issue fabOnReact/react-native-wear-connectivity#12).

react-native-wear-connectivity allows to develop android WearOS apps using react-native (for example develop for WearOS with react-native components like, ScrollView, Text etc..).

Most of the WearOS APIs are written in Compose, so I need to make those APIs available to React Native Developers. The most important ones are ScalingLazyColumn (which is based on LazyColumn) or WearableRecyclerView to support circular scrolling.

As explained in the above comments, I think it's hard to implement LazyColumn in React Native.

BUT I think I can quickly re-implement WearableRecyclerView following the same approach from react-native-recycler-view (which uses ViewGroup addView to add the items from FlatList renderItem to the RecyclerView).

I believe this is the same approach you are using in this library (you add a compose component to an Android ViewGroup using addView). I can use the same approach with WearableRecyclerView, so that we can support circular scrolling on React Native for WearOS.

Once I have that done, users can build WearOS apps using your Jetpack Components or the WearOS JetPack Compose components.

I opened this issue to collaborate and share toughts on how to implement LazyColumn in React Native.

@fabOnReact fabOnReact closed this as not planned Won't fix, can't repro, duplicate, stale Dec 21, 2024
@fabOnReact fabOnReact reopened this Jan 29, 2025
@fabOnReact
Copy link
Author

fabOnReact commented Jan 29, 2025

Hello! I am no expert, but Column doesn't scroll by default. I think you need LazyColumn for that to scroll. I tried implementing it but it was giving me errors so I stopped for now. If you can get it working that would be great!

As for limitations of compose in RN, it's just very untested. There are some kinks we need to work out and it doesn't always work how you'd expect. I'm hoping to solve most of the issues in this library though.

@andrew-levy it is not clear from the readme that you do not plan to support LazyColumn. Could you give me some feedback on that because I don't understand if you are interested in working on LazyColumn together or instead you don't plan to build a component around the JetPack Compose LazyColumn implementation.

@andrew-levy
Copy link
Owner

@fabOnReact we have a lazy column view already https://github.com/andrew-levy/jetpack-compose-react-native/blob/main/android/src/main/java/expo/modules/jetpackcomposereactnative/views/column/ColumnView.kt

@fabOnReact
Copy link
Author

#13

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

No branches or pull requests

2 participants