By the end of this lesson, you will be able to:
βΉοΈ Info Definition: Navigation and user flows define how users move through an app, discover features, and accomplish their goals. Great navigation feels invisible - users focus on their tasks, not figuring out how to navigate the app.
Navigation design can make or break an app:
Pattern | Best For | Examples |
---|---|---|
Tab Navigation | 3-5 main sections | Instagram, Twitter, Spotify |
Stack Navigation | Hierarchical content | Settings, product details |
Drawer Navigation | Many categories | Gmail, Medium |
Modal Navigation | Temporary tasks | Compose tweet, add photo |
Bottom Sheets | Quick actions | Google Maps, Apple Music |
π‘ UX Insight: Users form mental models of app navigation within 30 seconds. Match their expectations to reduce cognitive load!
# React Navigation v6
npm install @react-navigation/native
npm install @react-navigation/native-stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer
npm install @react-navigation/material-top-tabs
# Required dependencies
npx expo install react-native-screens react-native-safe-area-context
npx expo install react-native-gesture-handler
npx expo install react-native-reanimated
# Tab view for material top tabs
npm install react-native-tab-view
// navigation/AppNavigator.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { Ionicons } from '@expo/vector-icons';
// Import screens
import HomeScreen from '../screens/HomeScreen';
import ProfileScreen from '../screens/ProfileScreen';
import SearchScreen from '../screens/SearchScreen';
import NotificationsScreen from '../screens/NotificationsScreen';
import SettingsScreen from '../screens/SettingsScreen';
// Navigation parameter types
export type RootStackParamList = {
Main: undefined;
Profile: { userId: string };
Settings: undefined;
Details: { itemId: string; title: string };
};\n\nexport type MainTabParamList = {\n Home: undefined;\n Search: undefined;\n Notifications: undefined;\n Profile: undefined;\n};\n\nconst Stack = createNativeStackNavigator<RootStackParamList>();\nconst Tab = createBottomTabNavigator<MainTabParamList>();\nconst Drawer = createDrawerNavigator();\n\n// Main tab navigator\nconst MainTabs: React.FC = () => {\n return (\n <Tab.Navigator\n screenOptions={({ route }) => ({\n tabBarIcon: ({ focused, color, size }) => {\n let iconName: keyof typeof Ionicons.glyphMap;\n\n switch (route.name) {\n case 'Home':\n iconName = focused ? 'home' : 'home-outline';\n break;\n case 'Search':\n iconName = focused ? 'search' : 'search-outline';\n break;\n case 'Notifications':\n iconName = focused ? 'notifications' : 'notifications-outline';\n break;\n case 'Profile':\n iconName = focused ? 'person' : 'person-outline';\n break;\n default:\n iconName = 'circle';\n }\n\n return <Ionicons name={iconName} size={size} color={color} />;\n },\n tabBarActiveTintColor: '#007AFF',\n tabBarInactiveTintColor: '#8E8E93',\n tabBarStyle: {\n backgroundColor: '#FFFFFF',\n borderTopWidth: 0.5,\n borderTopColor: '#C6C6C8',\n paddingBottom: 5,\n paddingTop: 5,\n height: 60,\n },\n headerShown: false,\n })}\n >\n <Tab.Screen \n name=\"Home\" \n component={HomeScreen}\n options={{\n tabBarLabel: 'Home',\n tabBarBadge: undefined, // Can be used for notifications\n }}\n />\n <Tab.Screen \n name=\"Search\" \n component={SearchScreen}\n options={{\n tabBarLabel: 'Search',\n }}\n />\n <Tab.Screen \n name=\"Notifications\" \n component={NotificationsScreen}\n options={{\n tabBarLabel: 'Alerts',\n tabBarBadge: 3, // Example notification count\n }}\n />\n <Tab.Screen \n name=\"Profile\" \n component={ProfileScreen}\n options={{\n tabBarLabel: 'Profile',\n }}\n />\n </Tab.Navigator>\n );\n};\n\n// Main app navigator\nexport const AppNavigator: React.FC = () => {\n return (\n <NavigationContainer>\n <Stack.Navigator\n initialRouteName=\"Main\"\n screenOptions={{\n headerStyle: {\n backgroundColor: '#007AFF',\n },\n headerTintColor: '#FFFFFF',\n headerTitleStyle: {\n fontWeight: 'bold',\n },\n animation: 'slide_from_right',\n }}\n >\n <Stack.Screen \n name=\"Main\" \n component={MainTabs}\n options={{ headerShown: false }}\n />\n <Stack.Screen \n name=\"Settings\" \n component={SettingsScreen}\n options={{\n title: 'Settings',\n presentation: 'modal', // iOS-style modal presentation\n }}\n />\n </Stack.Navigator>\n </NavigationContainer>\n );\n};\n\nexport default AppNavigator;\n```\n\n## ποΈ Advanced Navigation Patterns\n\n### Custom Navigation Header\n\n```typescript\n// components/CustomHeader.tsx\nimport React from 'react';\nimport {\n View,\n Text,\n TouchableOpacity,\n StyleSheet,\n SafeAreaView,\n} from 'react-native';\nimport { Ionicons } from '@expo/vector-icons';\nimport { useNavigation } from '@react-navigation/native';\n\ninterface CustomHeaderProps {\n title: string;\n showBackButton?: boolean;\n rightAction?: {\n icon: keyof typeof Ionicons.glyphMap;\n onPress: () => void;\n };\n subtitle?: string;\n}\n\nexport const CustomHeader: React.FC<CustomHeaderProps> = ({\n title,\n showBackButton = false,\n rightAction,\n subtitle,\n}) => {\n const navigation = useNavigation();\n\n return (\n <SafeAreaView style={styles.container}>\n <View style={styles.header}>\n {/* Left side */}\n <View style={styles.leftContainer}>\n {showBackButton && (\n <TouchableOpacity\n style={styles.backButton}\n onPress={() => navigation.goBack()}\n >\n <Ionicons name=\"chevron-back\" size={24} color=\"#007AFF\" />\n </TouchableOpacity>\n )}\n </View>\n\n {/* Center */}\n <View style={styles.centerContainer}>\n <Text style={styles.title}>{title}</Text>\n {subtitle && (\n <Text style={styles.subtitle}>{subtitle}</Text>\n )}\n </View>\n\n {/* Right side */}\n <View style={styles.rightContainer}>\n {rightAction && (\n <TouchableOpacity\n style={styles.actionButton}\n onPress={rightAction.onPress}\n >\n <Ionicons name={rightAction.icon} size={24} color=\"#007AFF\" />\n </TouchableOpacity>\n )}\n </View>\n </View>\n </SafeAreaView>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n backgroundColor: '#FFFFFF',\n borderBottomWidth: 0.5,\n borderBottomColor: '#C6C6C8',\n },\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n paddingHorizontal: 16,\n paddingVertical: 12,\n minHeight: 44,\n },\n leftContainer: {\n flex: 1,\n alignItems: 'flex-start',\n },\n centerContainer: {\n flex: 2,\n alignItems: 'center',\n },\n rightContainer: {\n flex: 1,\n alignItems: 'flex-end',\n },\n backButton: {\n padding: 4,\n },\n title: {\n fontSize: 17,\n fontWeight: '600',\n color: '#000000',\n },\n subtitle: {\n fontSize: 13,\n color: '#8E8E93',\n marginTop: 2,\n },\n actionButton: {\n padding: 4,\n },\n});\n\nexport default CustomHeader;\n```\n\n### Bottom Sheet Navigation\n\n```typescript\n// components/BottomSheetMenu.tsx\nimport React, { useRef, useEffect } from 'react';\nimport {\n View,\n Text,\n TouchableOpacity,\n StyleSheet,\n Animated,\n Dimensions,\n PanGestureHandler,\n State,\n} from 'react-native';\nimport { Ionicons } from '@expo/vector-icons';\n\ninterface MenuItem {\n id: string;\n title: string;\n icon: keyof typeof Ionicons.glyphMap;\n onPress: () => void;\n destructive?: boolean;\n}\n\ninterface BottomSheetMenuProps {\n isVisible: boolean;\n onClose: () => void;\n menuItems: MenuItem[];\n title?: string;\n}\n\nexport const BottomSheetMenu: React.FC<BottomSheetMenuProps> = ({\n isVisible,\n onClose,\n menuItems,\n title,\n}) => {\n const translateY = useRef(new Animated.Value(SCREEN_HEIGHT)).current;\n const opacity = useRef(new Animated.Value(0)).current;\n\n useEffect(() => {\n if (isVisible) {\n showBottomSheet();\n } else {\n hideBottomSheet();\n }\n }, [isVisible]);\n\n const showBottomSheet = () => {\n Animated.parallel([\n Animated.timing(translateY, {\n toValue: 0,\n duration: 300,\n useNativeDriver: true,\n }),\n Animated.timing(opacity, {\n toValue: 1,\n duration: 300,\n useNativeDriver: true,\n }),\n ]).start();\n };\n\n const hideBottomSheet = () => {\n Animated.parallel([\n Animated.timing(translateY, {\n toValue: SCREEN_HEIGHT,\n duration: 300,\n useNativeDriver: true,\n }),\n Animated.timing(opacity, {\n toValue: 0,\n duration: 300,\n useNativeDriver: true,\n }),\n ]).start();\n };\n\n const handleGesture = (event: any) => {\n const { translationY, state } = event.nativeEvent;\n \n if (state === State.ACTIVE) {\n if (translationY > 0) {\n translateY.setValue(translationY);\n }\n } else if (state === State.END) {\n if (translationY > 100) {\n onClose();\n } else {\n showBottomSheet();\n }\n }\n };\n\n if (!isVisible) return null;\n\n return (\n <View style={styles.overlay}>\n {/* Background overlay */}\n <Animated.View \n style={[\n styles.backdrop,\n { opacity }\n ]}\n >\n <TouchableOpacity \n style={styles.backdropTouchable}\n onPress={onClose}\n />\n </Animated.View>\n\n {/* Bottom sheet */}\n <PanGestureHandler onGestureEvent={handleGesture}>\n <Animated.View \n style={[\n styles.bottomSheet,\n { transform: [{ translateY }] }\n ]}\n >\n {/* Handle */}\n <View style={styles.handle} />\n \n {/* Title */}\n {title && (\n <Text style={styles.title}>{title}</Text>\n )}\n\n {/* Menu items */}\n <View style={styles.menuItems}>\n {menuItems.map((item, index) => (\n <TouchableOpacity\n key={item.id}\n style={[\n styles.menuItem,\n index === menuItems.length - 1 && styles.lastMenuItem,\n ]}\n onPress={() => {\n item.onPress();\n onClose();\n }}\n >\n <Ionicons \n name={item.icon} \n size={24} \n color={item.destructive ? '#FF3B30' : '#007AFF'}\n style={styles.menuIcon}\n />\n <Text \n style={[\n styles.menuText,\n item.destructive && styles.destructiveText\n ]}\n >\n {item.title}\n </Text>\n </TouchableOpacity>\n ))}\n </View>\n </Animated.View>\n </PanGestureHandler>\n </View>\n );\n};\n\nconst SCREEN_HEIGHT = Dimensions.get('window').height;\n\nconst styles = StyleSheet.create({\n overlay: {\n ...StyleSheet.absoluteFillObject,\n zIndex: 1000,\n },\n backdrop: {\n ...StyleSheet.absoluteFillObject,\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n },\n backdropTouchable: {\n flex: 1,\n },\n bottomSheet: {\n position: 'absolute',\n bottom: 0,\n left: 0,\n right: 0,\n backgroundColor: '#FFFFFF',\n borderTopLeftRadius: 20,\n borderTopRightRadius: 20,\n paddingBottom: 34, // Safe area for iPhone\n },\n handle: {\n width: 40,\n height: 4,\n backgroundColor: '#C6C6C8',\n borderRadius: 2,\n alignSelf: 'center',\n marginTop: 12,\n marginBottom: 20,\n },\n title: {\n fontSize: 16,\n fontWeight: '600',\n color: '#000000',\n textAlign: 'center',\n marginBottom: 20,\n },\n menuItems: {\n paddingHorizontal: 20,\n },\n menuItem: {\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 16,\n borderBottomWidth: 0.5,\n borderBottomColor: '#C6C6C8',\n },\n lastMenuItem: {\n borderBottomWidth: 0,\n },\n menuIcon: {\n marginRight: 16,\n },\n menuText: {\n fontSize: 16,\n color: '#000000',\n flex: 1,\n },\n destructiveText: {\n color: '#FF3B30',\n },\n});\n\nexport default BottomSheetMenu;\n```\n\n## π Advanced User Flow Patterns\n\n### Onboarding Flow with Progress\n\n```typescript\n// components/OnboardingFlow.tsx\nimport React, { useState } from 'react';\nimport {\n View,\n Text,\n TouchableOpacity,\n StyleSheet,\n Dimensions,\n Animated,\n ScrollView,\n} from 'react-native';\nimport { LinearGradient } from 'expo-linear-gradient';\nimport { Ionicons } from '@expo/vector-icons';\n\ninterface OnboardingStep {\n id: string;\n title: string;\n description: string;\n icon: keyof typeof Ionicons.glyphMap;\n action?: {\n title: string;\n onPress: () => void;\n };\n}\n\nconst onboardingSteps: OnboardingStep[] = [\n {\n id: '1',\n title: 'Welcome to the App',\n description: 'Discover amazing features and connect with friends in a whole new way.',\n icon: 'rocket-outline',\n },\n {\n id: '2',\n title: 'Stay Connected',\n description: 'Get real-time notifications about what matters most to you.',\n icon: 'notifications-outline',\n action: {\n title: 'Enable Notifications',\n onPress: () => console.log('Enable notifications'),\n },\n },\n {\n id: '3',\n title: 'Personalize Your Experience',\n description: 'Customize your profile and preferences to make the app truly yours.',\n icon: 'person-outline',\n },\n {\n id: '4',\n title: 'You\\'re All Set!',\n description: 'Start exploring and make the most of your new app experience.',\n icon: 'checkmark-circle-outline',\n },\n];\n\nexport const OnboardingFlow: React.FC<{\n onComplete: () => void;\n}> = ({ onComplete }) => {\n const [currentStep, setCurrentStep] = useState(0);\n const [fadeAnim] = useState(new Animated.Value(1));\n const scrollViewRef = React.useRef<ScrollView>(null);\n\n const animateStepTransition = (callback: () => void) => {\n Animated.timing(fadeAnim, {\n toValue: 0,\n duration: 200,\n useNativeDriver: true,\n }).start(() => {\n callback();\n Animated.timing(fadeAnim, {\n toValue: 1,\n duration: 200,\n useNativeDriver: true,\n }).start();\n });\n };\n\n const nextStep = () => {\n if (currentStep < onboardingSteps.length - 1) {\n animateStepTransition(() => {\n setCurrentStep(currentStep + 1);\n });\n } else {\n onComplete();\n }\n };\n\n const previousStep = () => {\n if (currentStep > 0) {\n animateStepTransition(() => {\n setCurrentStep(currentStep - 1);\n });\n }\n };\n\n const skipToEnd = () => {\n onComplete();\n };\n\n const currentStepData = onboardingSteps[currentStep];\n\n return (\n <LinearGradient\n colors={['#667eea', '#764ba2']}\n style={styles.container}\n >\n {/* Header */}\n <View style={styles.header}>\n <TouchableOpacity onPress={skipToEnd}>\n <Text style={styles.skipText}>Skip</Text>\n </TouchableOpacity>\n </View>\n\n {/* Progress indicator */}\n <View style={styles.progressContainer}>\n <View style={styles.progressBar}>\n <View \n style={[\n styles.progressFill,\n { width: `${((currentStep + 1) / onboardingSteps.length) * 100}%` }\n ]}\n />\n </View>\n <Text style={styles.progressText}>\n {currentStep + 1} of {onboardingSteps.length}\n </Text>\n </View>\n\n {/* Content */}\n <Animated.View style={[styles.content, { opacity: fadeAnim }]}>\n <View style={styles.iconContainer}>\n <Ionicons \n name={currentStepData.icon} \n size={80} \n color=\"white\" \n />\n </View>\n \n <Text style={styles.title}>{currentStepData.title}</Text>\n <Text style={styles.description}>{currentStepData.description}</Text>\n \n {currentStepData.action && (\n <TouchableOpacity \n style={styles.actionButton}\n onPress={currentStepData.action.onPress}\n >\n <Text style={styles.actionButtonText}>\n {currentStepData.action.title}\n </Text>\n </TouchableOpacity>\n )}\n </Animated.View>\n\n {/* Navigation */}\n <View style={styles.navigation}>\n <TouchableOpacity \n style={[\n styles.navButton,\n currentStep === 0 && styles.navButtonDisabled\n ]}\n onPress={previousStep}\n disabled={currentStep === 0}\n >\n <Text style={[\n styles.navButtonText,\n currentStep === 0 && styles.navButtonTextDisabled\n ]}>Previous</Text>\n </TouchableOpacity>\n \n <TouchableOpacity \n style={styles.nextButton}\n onPress={nextStep}\n >\n <Text style={styles.nextButtonText}>\n {currentStep === onboardingSteps.length - 1 ? 'Get Started' : 'Next'}\n </Text>\n </TouchableOpacity>\n </View>\n </LinearGradient>\n );\n};\n\nconst { width, height } = Dimensions.get('window');\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n paddingHorizontal: 30,\n },\n header: {\n flexDirection: 'row',\n justifyContent: 'flex-end',\n paddingTop: 50,\n paddingBottom: 20,\n },\n skipText: {\n color: 'rgba(255, 255, 255, 0.8)',\n fontSize: 16,\n },\n progressContainer: {\n marginBottom: 60,\n },\n progressBar: {\n height: 4,\n backgroundColor: 'rgba(255, 255, 255, 0.3)',\n borderRadius: 2,\n overflow: 'hidden',\n marginBottom: 10,\n },\n progressFill: {\n height: '100%',\n backgroundColor: 'white',\n borderRadius: 2,\n },\n progressText: {\n color: 'rgba(255, 255, 255, 0.8)',\n fontSize: 14,\n textAlign: 'center',\n },\n content: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n },\n iconContainer: {\n marginBottom: 40,\n },\n title: {\n fontSize: 28,\n fontWeight: 'bold',\n color: 'white',\n textAlign: 'center',\n marginBottom: 20,\n },\n description: {\n fontSize: 16,\n color: 'rgba(255, 255, 255, 0.9)',\n textAlign: 'center',\n lineHeight: 24,\n marginBottom: 40,\n },\n actionButton: {\n backgroundColor: 'rgba(255, 255, 255, 0.2)',\n paddingHorizontal: 30,\n paddingVertical: 15,\n borderRadius: 25,\n borderWidth: 1,\n borderColor: 'rgba(255, 255, 255, 0.3)',\n },\n actionButtonText: {\n color: 'white',\n fontSize: 16,\n fontWeight: '600',\n },\n navigation: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n paddingBottom: 50,\n },\n navButton: {\n paddingHorizontal: 20,\n paddingVertical: 15,\n },\n navButtonDisabled: {\n opacity: 0.5,\n },\n navButtonText: {\n color: 'white',\n fontSize: 16,\n },\n navButtonTextDisabled: {\n color: 'rgba(255, 255, 255, 0.5)',\n },\n nextButton: {\n backgroundColor: 'white',\n paddingHorizontal: 30,\n paddingVertical: 15,\n borderRadius: 25,\n },\n nextButtonText: {\n color: '#667eea',\n fontSize: 16,\n fontWeight: 'bold',\n },\n});\n\nexport default OnboardingFlow;\n```\n\n## π€ AI-Powered Navigation Analytics\n\n### User Flow Analysis Service\n\n```typescript\n// services/NavigationAnalytics.ts\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport OpenAI from 'openai';\n\ninterface NavigationEvent {\n screen: string;\n timestamp: number;\n action: 'navigate' | 'back' | 'tab_switch' | 'deep_link';\n source?: string;\n metadata?: Record<string, any>;\n}\n\ninterface UserSession {\n sessionId: string;\n startTime: number;\n endTime?: number;\n events: NavigationEvent[];\n userId?: string;\n}\n\nclass NavigationAnalytics {\n private currentSession: UserSession | null = null;\n private openai: OpenAI;\n\n constructor(apiKey: string) {\n this.openai = new OpenAI({ apiKey });\n this.initializeSession();\n }\n\n private async initializeSession() {\n this.currentSession = {\n sessionId: Date.now().toString(),\n startTime: Date.now(),\n events: [],\n };\n }\n\n trackNavigation(\n screen: string, \n action: NavigationEvent['action'], \n source?: string,\n metadata?: Record<string, any>\n ) {\n if (!this.currentSession) return;\n\n const event: NavigationEvent = {\n screen,\n timestamp: Date.now(),\n action,\n source,\n metadata,\n };\n\n this.currentSession.events.push(event);\n this.saveSession();\n }\n\n private async saveSession() {\n if (!this.currentSession) return;\n \n try {\n const sessions = await this.getAllSessions();\n sessions.push(this.currentSession);\n \n // Keep only last 100 sessions\n const recentSessions = sessions.slice(-100);\n \n await AsyncStorage.setItem(\n 'navigation_sessions', \n JSON.stringify(recentSessions)\n );\n } catch (error) {\n console.error('Error saving navigation session:', error);\n }\n }\n\n private async getAllSessions(): Promise<UserSession[]> {\n try {\n const sessions = await AsyncStorage.getItem('navigation_sessions');\n return sessions ? JSON.parse(sessions) : [];\n } catch (error) {\n console.error('Error loading navigation sessions:', error);\n return [];\n }\n }\n\n async endSession() {\n if (!this.currentSession) return;\n \n this.currentSession.endTime = Date.now();\n await this.saveSession();\n this.currentSession = null;\n }\n\n async analyzeUserFlow(): Promise<{\n commonPaths: string[];\n dropOffPoints: string[];\n insights: string[];\n recommendations: string[];\n }> {\n const sessions = await this.getAllSessions();\n \n if (sessions.length === 0) {\n return {\n commonPaths: [],\n dropOffPoints: [],\n insights: ['Not enough data to analyze user flow'],\n recommendations: [],\n };\n }\n\n // Analyze common navigation patterns\n const paths: string[] = [];\n const screenCounts: Record<string, number> = {};\n \n sessions.forEach(session => {\n const sessionPath = session.events\n .filter(e => e.action === 'navigate')\n .map(e => e.screen)\n .join(' β ');\n \n if (sessionPath) paths.push(sessionPath);\n \n session.events.forEach(event => {\n screenCounts[event.screen] = (screenCounts[event.screen] || 0) + 1;\n });\n });\n\n // Find most common paths\n const pathCounts: Record<string, number> = {};\n paths.forEach(path => {\n pathCounts[path] = (pathCounts[path] || 0) + 1;\n });\n \n const commonPaths = Object.entries(pathCounts)\n .sort(([,a], [,b]) => b - a)\n .slice(0, 5)\n .map(([path]) => path);\n\n // Use AI for deeper insights\n const aiInsights = await this.generateAIInsights(sessions);\n \n return {\n commonPaths,\n dropOffPoints: this.findDropOffPoints(sessions),\n insights: aiInsights.insights,\n recommendations: aiInsights.recommendations,\n };\n }\n\n private findDropOffPoints(sessions: UserSession[]): string[] {\n const exitCounts: Record<string, number> = {};\n const visitCounts: Record<string, number> = {};\n \n sessions.forEach(session => {\n const events = session.events.filter(e => e.action === 'navigate');\n \n events.forEach(event => {\n visitCounts[event.screen] = (visitCounts[event.screen] || 0) + 1;\n });\n \n // Last screen is a potential drop-off point\n if (events.length > 0) {\n const lastScreen = events[events.length - 1].screen;\n exitCounts[lastScreen] = (exitCounts[lastScreen] || 0) + 1;\n }\n });\n \n // Find screens with high exit rates\n return Object.entries(exitCounts)\n .map(([screen, exits]) => ({\n screen,\n exitRate: exits / (visitCounts[screen] || 1)\n }))\n .filter(({ exitRate }) => exitRate > 0.5)\n .sort((a, b) => b.exitRate - a.exitRate)\n .slice(0, 3)\n .map(({ screen }) => screen);\n }\n\n private async generateAIInsights(sessions: UserSession[]): Promise<{\n insights: string[];\n recommendations: string[];\n }> {\n if (sessions.length === 0) {\n return { insights: [], recommendations: [] };\n }\n\n const sessionSummary = {\n totalSessions: sessions.length,\n avgSessionLength: sessions.reduce((sum, s) => sum + (s.endTime || Date.now()) - s.startTime, 0) / sessions.length,\n mostVisitedScreens: this.getMostVisitedScreens(sessions),\n commonNavigationPatterns: this.getNavigationPatterns(sessions),\n };\n\n const prompt = `\n Analyze this mobile app navigation data:\n \n Sessions: ${sessionSummary.totalSessions}\n Average session length: ${Math.round(sessionSummary.avgSessionLength / 1000)}s\n Most visited screens: ${sessionSummary.mostVisitedScreens.join(', ')}\n Common patterns: ${sessionSummary.commonNavigationPatterns.slice(0, 3).join('; ')}\n \n Provide insights about user behavior and recommendations for improving navigation.\n Return as JSON with 'insights' and 'recommendations' arrays.\n `;\n\n try {\n const response = await this.openai.chat.completions.create({\n model: 'gpt-3.5-turbo',\n messages: [{ role: 'user', content: prompt }],\n temperature: 0.3,\n });\n\n return JSON.parse(response.choices[0].message.content || '{}');\n } catch (error) {\n console.error('AI insights generation error:', error);\n return {\n insights: ['Unable to generate AI insights'],\n recommendations: ['Review navigation patterns manually'],\n };\n }\n }\n\n private getMostVisitedScreens(sessions: UserSession[]): string[] {\n const screenCounts: Record<string, number> = {};\n \n sessions.forEach(session => {\n session.events.forEach(event => {\n screenCounts[event.screen] = (screenCounts[event.screen] || 0) + 1;\n });\n });\n \n return Object.entries(screenCounts)\n .sort(([,a], [,b]) => b - a)\n .slice(0, 5)\n .map(([screen]) => screen);\n }\n\n private getNavigationPatterns(sessions: UserSession[]): string[] {\n const patterns: Record<string, number> = {};\n \n sessions.forEach(session => {\n const navigationEvents = session.events\n .filter(e => e.action === 'navigate')\n .map(e => e.screen);\n \n for (let i = 0; i < navigationEvents.length - 1; i++) {\n const pattern = `${navigationEvents[i]} β ${navigationEvents[i + 1]}`;\n patterns[pattern] = (patterns[pattern] || 0) + 1;\n }\n });\n \n return Object.entries(patterns)\n .sort(([,a], [,b]) => b - a)\n .slice(0, 5)\n .map(([pattern]) => pattern);\n }\n}\n\nexport default NavigationAnalytics;\n```\n\n## π Summary\n\nIn this lesson, you learned:\n- How to design and implement intuitive navigation systems\n- Advanced navigation patterns including tabs, stacks, drawers, and bottom sheets\n- Creating seamless user flows with proper information architecture\n- Building custom navigation components and interactions\n- Using AI to analyze user behavior and optimize navigation\n- Implementing accessibility-first navigation principles\n\n## π€ Practice with AI\n\nCode with AI: Try building these advanced navigation features.\n\n**Prompts to try:**\n- *\"Create a multi-level navigation system with breadcrumbs for complex app hierarchies\"*\n- *\"Build a dynamic tab bar that adapts based on user preferences and behavior\"*\n- *\"Design a voice-controlled navigation system for accessibility\"*\n- *\"Implement a smart search navigation that learns from user query patterns\"*\n- *\"Create a context-aware navigation that shows different options based on user location and time\"*\n\nGreat navigation is invisible to users but crucial for app success - invest in getting it right!"