Developing a Content Feed in a Mobile App
The feed is the screen users see most frequently and interact with during every session. Performance is critical here: 60 fps when scrolling through hundreds of cards with images, videos, and variable-length text. A single unoptimized render — and users perceive the app as "sluggish," without understanding why.
Rendering Performance
React Native FlashList. The standard FlatList has a known limitation — JS-side recycling. FlashList by Shopify solves this through the native RecyclerListView protocol. Key parameters: estimatedItemSize (critical for performance — set the average card height, otherwise initial layout calculates incorrectly), overrideItemLayout for cards with known dimensions, drawDistance to manage the rendering buffer.
Diverse card types (photo post, video post, ad block, stories) require getItemType — FlashList creates a separate reuse pool for each type. Without this, the video player from a "video" card ends up in a "text" card.
Jetpack Compose LazyColumn. The key by element id is mandatory — without it, Compose cannot efficiently compare elements during updates. contentType is analogous to getItemType. For images — AsyncImage from Coil 2.x with placeholder and error fallback. rememberLazyListState() preserves scroll position during configuration changes.
Flutter SliverList. SliverList.builder inside CustomScrollView — the correct approach for feeds with diverse content: you can add SliverAppBar with parallax, SliverPersistentHeader for sticky headers. AutomaticKeepAliveClientMixin in video cards — so the player is not recreated when leaving the viewport.
Video Autoplay
The main pain point for video feeds. You need to play the video that's most visible (largest visible area in the viewport) and stop the others.
In React Native: IntersectionObserver-like logic through onViewableItemsChanged in FlatList with viewabilityConfig: { itemVisiblePercentThreshold: 60 }. Determine viewableItems, pass isActive prop to the card. In the card — Video component from react-native-video with paused={!isActive}.
On Android — ExoPlayer (Media3) with RecyclerView.OnScrollListener. PlayerView in each card reuses a single ExoPlayer instance through ExoPlayer.Builder().build() at the adapter level.
From practice: news aggregator, React Native. Feed of 50+ cards, some with video. Stuttered on Android during scrolling — FPS dropped to 30 on Xiaomi Redmi 9. Profiled through Android Studio Profiler: main load on Bridge thread from frequent onScroll events. Added scrollEventThrottle={32} (30 fps for scroll events), replaced FlatList with FlashList — stable 60 fps.
Feed Algorithm and Infinite Scroll
An infinite scroll feed requires handling multiple states: initial load (skeleton), next page load (footer indicator), pull-to-refresh update (top spinner), empty feed (empty state with CTA), load error (retry button).
Prefetch: start loading the next page when 3–5 cards remain to the end of the list. Don't wait for the user to hit the spinner.
Optimistic reactions (likes, reposts): update UI instantly, request goes in the background. On error — rollback with shake animation. Users don't wait for server confirmation for simple actions.
Card Types
| Type | Technical Features |
|---|---|
| Photo | Lazy load + placeholder, progressive JPEG via FastImage |
| Video | Thumbnail before playback, ExoPlayer / AVPlayer |
| Gallery | Horizontal PageView/ViewPager inside vertical list |
| Text | expandable with "Read more" when exceeding N lines |
| Link preview | OGP data: image, title, domain |
Scope of Work
- Feed with support for diverse card types
- Infinite scroll with cursor-based pagination
- Pull-to-refresh
- Video autoplay based on visibility
- Likes, reposts, bookmarks with optimistic updates
- Skeleton loading for first render
- Algorithmic or chronological feed — as per API requirements
- Prefetch next page
Timeline
3–5 business days — depends on the number of card types and presence of video content. Feed with text and photos only — 3 days. With video and diverse cards — 4–5 days. Cost calculated individually.







