Introduction
In Part 1, we covered the key metrics for measuring fluidity and responsiveness. In Part 2, we identified performance issues in the home screen implementation using the KPI Visualizer and Perfetto traces. Now let’s fix those problems.
This article shows the optimized implementation and explains why it works.
Sample Code: The examples in this series are based on the Vega Developer Workshop for TV Apps repository. The README includes a hands-on workshop you can follow along with if you’d like to experiment with these patterns yourself.
The Optimized Implementation
In Part 2, we identified that the degraded home screen had:
- 97% fluidity (target: 99%+)
- 41.4ms max focus response (target: <16ms)
- 19 instances of 3+ consecutive dropped frames
- ~50 fps (target: 60fps)
The root cause was blocking work and non-native animations in focus handlers, combined with an unoptimized FlatList.
Optimized HomeScreen with Carousel
Here’s the optimized version using Vega’s Carousel component:
import {Carousel, ItemInfo} from '@amazon-devices/kepler-ui-components';
const HomeScreen = ({navigation}) => {
const [contentRows, setContentRows] = useState([]);
// ... data fetching code ...
return (
<ScrollView style={styles.container}>
{contentRows.map((row, rowIndex) => (
<ContentRowComponent
key={row.title}
row={row}
onItemPress={handleItemPress}
/>
))}
</ScrollView>
);
};
const ContentRowComponent = ({row, onItemPress}) => {
const renderItem = ({item, index}) => {
return (
<MovieCard
item={item}
onPress={() => onItemPress(item)}
/>
);
};
const itemInfo: ItemInfo[] = [
{
view: MovieCard,
dimension: {width: ITEM_WIDTH, height: ITEM_HEIGHT},
},
];
return (
<View style={styles.rowContainer}>
<Text style={styles.rowHeader}>{row.title}</Text>
<Carousel
data={row.items}
orientation="horizontal"
itemDimensions={itemInfo}
renderItem={renderItem}
getItemForIndex={() => MovieCard}
keyProvider={(item, index) => `${index}-${item.id}`}
focusIndicatorType="fixed"
/>
</View>
);
};
Optimized MovieCard with Simple Focus Handlers
// Memoized to prevent unnecessary re-renders
const MovieCard = React.memo(({item, onPress}) => {
const [focused, setFocused] = useState(false);
// GOOD: Minimal focus handler - just updates state
// No blocking work, no animations
return (
<Pressable
style={[styles.card, focused && styles.cardFocused]}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
onPress={onPress}>
<Image
source={{uri: item.images.thumbnail_450x253}}
style={styles.thumbnail}
resizeMode="cover"
/>
</Pressable>
);
});
Why This Works
Fluidity Improvements:
| Fix | Why It Helps |
|---|---|
Vega’s Carousel component |
Native-level performance with built-in recycling and focus management |
itemDimensions prop |
Eliminates dynamic measurement during scroll |
React.memo() on MovieCard |
Prevents re-renders when props haven’t changed |
| No new object references | Stable props don’t trigger unnecessary updates |
Responsiveness Improvements:
| Fix | Why It Helps |
|---|---|
| No blocking work in handlers | Focus events complete in <2ms instead of 50ms |
| No non-native animations | JS thread stays free for input processing |
| Pre-defined styles | No inline style computation on every render |
| Simple state updates | setFocused(true) is fast and non-blocking |
Results After Optimization
The improvement is clear after re-testing with the KPI Visualizer and analyzing Perfetto events:
| Metric | Degraded | Optimized | Improvement |
|---|---|---|---|
| Max event duration | 52.66ms | 6.76ms | 87% faster |
| Avg event duration | 17.39ms | 0.61ms | 96% faster |
| Metric | Degraded | Optimized | Improvement |
|---|---|---|---|
| Fluidity % | 97% | 100% | 3% improvement |
| Max frame duration | 62.94ms | 7.54ms | 88% faster |
| Frames over 16ms | 6 | 0 | 100% fewer |
The optimized version achieves 100% fluidity by keeping the JS thread free for rendering and input processing.
Summary
Building fluid and responsive apps for Fire TV requires attention to how your app feels, not just how fast it runs.
Key takeaways from this series:
-
Use the right components: Vega’s
Carouselis optimized for TV; prefer it overFlatListfor content rows. -
Keep focus handlers simple: Just update visual state. Defer heavy work with
InteractionManager.runAfterInteractions(). -
Use native-driven animations: Always set
useNativeDriver: true. -
Memoize appropriately: Use
React.memo(),useCallback(), anduseMemo(). -
Measure regularly: Run KPI Visualizer tests throughout development.
-
Use Perfetto for deep analysis: When KPIs indicate problems, traces reveal the root cause.
For comprehensive optimization guidance, see App Performance Best Practices.
Additional Resources
Performance Measurement
Optimization Guidance
Debugging Tools
Last updated: December 2025