UTA DevHub
Guides

Finalization Stage Guide

Implementation patterns for the Finalization stage in the application bootstrap process.

Finalization Stage Guide

Overview

The Finalization stage is the final phase in the application bootstrap process, occurring after all background tasks have completed. This stage focuses on transitioning the application to a fully interactive state, completing any remaining initialization requirements, and ensuring the app is ready for unrestricted user interaction. This guide provides implementation patterns for the Finalization stage to ensure a smooth user experience.

Quick Start

To implement the Finalization stage:

  1. Register tasks with the initializer:

    import { initializer, InitStage } from '@/core/shared/app/initialization';
     
    // Register a Finalization stage task
    initializer.registerTask(InitStage.FINALIZATION, {
      name: 'ui:cleanup',
      priority: 'high',
      execute: async () => {
        // Remove loading indicators
        loadingStore.setGlobalLoading(false);
        
        // Enable deferred interactions
        uiStore.setFullyInteractive(true);
        
        // Log completion metrics
        analyticsService.logEvent('app_initialization_complete', {
          totalTime: performanceStore.getTotalInitTime()
        });
      }
    });
  2. Execute the Finalization stage after Background tasks complete:

    // In your initialization controller
    async function completeInitialization() {
      try {
        // Execute Finalization stage
        await initializer.executeStage(InitStage.FINALIZATION);
        
        // Mark initialization as complete
        initializationStore.setInitialized(true);
        
      } catch (error) {
        console.error('Finalization failed:', error);
        // Still mark as initialized to prevent app from being stuck
        initializationStore.setInitialized(true);
        // Log error for monitoring
        errorReporting.logError('initialization_finalization_failed', error);
      }
    }
  3. Use the initialized flag from the initialization store to remove loading overlays

When To Use

Add initialization tasks to the Finalization stage when they:

  • Complete UI transitions: Remove loading indicators, show fully loaded content
  • Enable deferred interactions: Activate features that were disabled during initialization
  • Report completion metrics: Send analytics about initialization performance
  • Perform post-initialization cleanup: Release temporary resources or states
  • Start monitoring services: Begin periodic background services

The Finalization stage should focus on making the transition from "initializing" to "fully functional" as seamless as possible. At this point, all critical initialization from the pre-ui, initial-ui, and background stages should be complete.

Implementation Patterns

As shown in the diagram, the Finalization stage begins after the background completes and represents the final transition to a fully initialized application state.

Building on the navigation setup from the initial-ui, the Finalization stage focuses on removing any loading overlays and enabling full navigation:

// components/InitializationAwareWrapper.tsx (Expo Router)
import React from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import { useInitialization } from '@/core/shared/app/initialization';
 
export function InitializationAwareWrapper({ children }) {
  const { initialized } = useInitialization();
  
  // App is fully initialized
  if (initialized) {
    return children;
  }
  
  // Initialization is still in progress
  return (
    <View style={styles.container}>
      {children}
      <View style={styles.loadingOverlay}>
        <ActivityIndicator size="large" color="#0000ff" />
      </View>
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  loadingOverlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(255, 255, 255, 0.7)',
    justifyContent: 'center',
    alignItems: 'center',
  },
});
 
// Usage in Expo Router layout
// app/(app)/_layout.tsx
export default function AppLayout() {
  return (
    <InitializationAwareWrapper>
      <Stack />
    </InitializationAwareWrapper>
  );
}

Removing Loading Indicators

The most visible aspect of the Finalization stage is removing loading indicators that were created during the initial-ui:

// core/shared/app/initialization/finalization.ts
import { initializer, InitStage, InitTask } from './initializer';
import { initEvents } from './events';
 
const uiFinalizationTask: InitTask = {
  name: 'ui:finalization',
  stage: InitStage.FINALIZATION,
  priority: 'high',
  
  async execute(): Promise<void> {
    try {
      // Notify UI components that initialization is complete
      initEvents.emit('initialization:complete');
      
      // Allow a short delay for UI transitions
      await new Promise(resolve => setTimeout(resolve, 150));
      
      // Remove any global loading indicators
      LoadingManager.hideGlobalLoadingIndicator();
      
      // Enable deferred UI interactions
      UIManager.setInteractionsEnabled(true);
    } catch (error) {
      console.error('UI finalization failed:', error);
      // Try to ensure UI is usable even if finalization fails
      LoadingManager.hideGlobalLoadingIndicator();
      UIManager.setInteractionsEnabled(true);
    }
  }
};
 
// Register task
initializer.registerTask(InitStage.FINALIZATION, uiFinalizationTask);

Initialization Performance Metrics

Collect and report metrics about the initialization process:

// core/shared/app/initialization/metrics.ts
import { initializer, InitStage, InitTask } from './initializer';
import { initEvents } from './events';
import { analyticsService } from '@/core/shared/analytics/service';
import { performanceStore } from '@/core/shared/performance/store';
 
// Collect and report initialization performance metrics
const metricsCollectionTask: InitTask = {
  name: 'metrics:initialization-performance',
  stage: InitStage.FINALIZATION,
  priority: 'normal',
  
  async execute(): Promise<void> {
    try {
      // Get timing data from performance store
      const timings = performanceStore.getInitializationTimings();
      
      // Calculate key metrics
      const totalInitTime = timings.total;
      const timeToInteractive = timings.stages[InitStage.PRE_UI] + 
                               timings.stages[InitStage.INITIAL_UI];
      const backgroundTime = timings.stages[InitStage.BACKGROUND];
      
      // Get task-specific timings
      const slowestTasks = calculateSlowestTasks(timings.tasks);
      
      // Report metrics to analytics
      analyticsService.trackEvent('initialization_complete', {
        total_init_time_ms: totalInitTime,
        time_to_interactive_ms: timeToInteractive,
        background_time_ms: backgroundTime,
        slowest_task: slowestTasks[0]?.name,
        slowest_task_time_ms: slowestTasks[0]?.duration,
        device_model: DeviceInfo.getModel(),
        os_version: DeviceInfo.getSystemVersion(),
        app_version: DeviceInfo.getVersion(),
        network_type: networkService.getNetworkType(),
      });
      
      // Log to console in development mode
      if (__DEV__) {
        console.log('Initialization performance metrics:', {
          totalInitTime: `${totalInitTime}ms`,
          timeToInteractive: `${timeToInteractive}ms`,
          backgroundTime: `${backgroundTime}ms`,
          slowestTasks: slowestTasks.map(t => `${t.name}: ${t.duration}ms`).join(', '),
        });
      }
      
      // Store metrics for developer performance tools
      if (configStore.getConfig().features.developerTools) {
        await storage.set('dev:last-init-metrics', JSON.stringify({
          timestamp: Date.now(),
          metrics: {
            totalInitTime,
            timeToInteractive,
            backgroundTime,
            taskTimings: timings.tasks,
          },
        }));
      }
    } catch (error) {
      console.error('Metrics collection failed:', error);
      // Non-critical - app can function without metrics
    }
  }
};
 
// Helper function to calculate slowest tasks
function calculateSlowestTasks(taskTimings) {
  return Object.entries(taskTimings)
    .map(([name, duration]) => ({ name, duration }))
    .sort((a, b) => b.duration - a.duration)
    .slice(0, 5); // Top 5 slowest tasks
}
 
// Register task
initializer.registerTask(InitStage.FINALIZATION, metricsCollectionTask);

Starting Monitoring Services

Start monitoring services after initialization completes:

// core/shared/monitoring/initialization.ts
import { initializer, InitStage, InitTask } from '@/core/shared/app/initialization';
import { monitoringService } from './service';
 
const monitoringStartTask: InitTask = {
  name: 'monitoring:start',
  stage: InitStage.FINALIZATION,
  priority: 'low',
  
  async execute(): Promise<void> {
    try {
      // Start performance monitoring
      monitoringService.startPerformanceMonitoring({
        sampleRate: 0.1, // Monitor 10% of sessions
        captureFrameMetrics: true,
        networkRequestSampling: true,
      });
      
      // Start error monitoring
      monitoringService.startErrorMonitoring({
        unhandledRejections: true,
        unhandledExceptions: true,
      });
      
      // Start network monitoring
      monitoringService.startNetworkMonitoring({
        excludeUrls: [
          // Exclude high-volume or sensitive endpoints
          /\/analytics\//,
          /\/logs\//,
        ],
        includeHeaders: false, // Don't capture headers for privacy
      });
      
      // Register app state change handler
      AppState.addEventListener('change', (state) => {
        monitoringService.recordAppStateChange(state);
        
        if (state === 'active') {
          // App came to foreground, resume monitoring
          monitoringService.resumeMonitoring();
        } else if (state === 'background') {
          // App went to background, pause monitoring to save resources
          monitoringService.pauseMonitoring();
        }
      });
    } catch (error) {
      console.error('Monitoring service start failed:', error);
      // Non-critical - app can function without monitoring
    }
  }
};
 
// Register task
initializer.registerTask(InitStage.FINALIZATION, monitoringStartTask);

Cleanup and Optimization

Perform cleanup and optimization in the Finalization stage:

// core/shared/app/initialization/cleanup.ts
import { initializer, InitStage, InitTask } from './initializer';
 
const cleanupTask: InitTask = {
  name: 'system:cleanup',
  stage: InitStage.FINALIZATION,
  priority: 'low',
  
  async execute(): Promise<void> {
    try {
      // Clear temporary initialization data
      await storage.remove('app:initialization-temp');
      
      // Release any initialization-specific resources
      initializer.releaseResources();
      
      // Run garbage collection if possible (this is platform specific)
      if (global.gc) {
        global.gc();
      }
      
      // Clean up old cached files
      await cleanupCachedFiles();
      
      // Clear temporary debug logs
      if (!__DEV__) {
        await cleanupDebugLogs();
      }
    } catch (error) {
      console.error('Cleanup failed:', error);
      // Non-critical - app can function without cleanup
    }
  }
};
 
// Helper to clean up cached files
async function cleanupCachedFiles() {
  try {
    // Get cache info
    const cacheInfo = await fileSystem.getCacheInfo();
    
    // If cache exceeds threshold, clear older files
    if (cacheInfo.size > MAX_CACHE_SIZE) {
      const filesToRemove = await fileSystem.getOldCacheFiles(
        MAX_CACHE_AGE_DAYS
      );
      
      await Promise.all(
        filesToRemove.map(file => fileSystem.removeFile(file.path))
      );
      
      console.log(`Cleaned up ${filesToRemove.length} old cached files`);
    }
  } catch (error) {
    console.error('Cache cleanup failed:', error);
  }
}
 
// Helper to clean up debug logs
async function cleanupDebugLogs() {
  try {
    const logFiles = await fileSystem.getFiles(DEBUG_LOGS_DIR);
    const oldLogFiles = logFiles.filter(
      file => (Date.now() - file.created) > MAX_LOG_AGE_DAYS * 24 * 60 * 60 * 1000
    );
    
    await Promise.all(
      oldLogFiles.map(file => fileSystem.removeFile(file.path))
    );
  } catch (error) {
    console.error('Log cleanup failed:', error);
  }
}
 
// Register task
initializer.registerTask(InitStage.FINALIZATION, cleanupTask);

Deferred Feature Initialization

Initialize non-critical features in the Finalization stage:

// features/chat/initialization.ts
import { initializer, InitStage, InitTask } from '@/core/shared/app/initialization';
import { chatService } from './service';
import { useAuthStore } from '@/core/domains/auth/store';
 
// Initialize chat system in the finalization stage
const chatInitTask: InitTask = {
  name: 'features:chat-init',
  stage: InitStage.FINALIZATION,
  priority: 'normal',
  dependencies: ['auth:token-retrieval'], // Depends on auth
  
  async execute(): Promise<void> {
    try {
      // Only initialize chat if user is logged in
      const authState = useAuthStore.getState();
      if (!authState.isAuthenticated) {
        return; // Exit early if not authenticated
      }
      
      // Initialize chat service
      await chatService.initialize({
        userId: authState.user.id,
        userName: authState.user.displayName,
        enablePushNotifications: true,
      });
      
      // Connect to chat servers
      await chatService.connect();
      
      // Register for chat events
      chatService.onNewMessage((message) => {
        // Update UI, show notification, etc.
        messagingStore.addMessage(message);
        
        // Show in-app notification if app is in foreground
        if (AppState.currentState === 'active') {
          notificationService.showInAppNotification({
            title: message.sender.name,
            message: message.text,
            onPress: () => {
              navigationService.navigate('Chat', {
                conversationId: message.conversationId,
              });
            },
          });
        }
      });
      
      // Fetch unread message count
      const unreadCount = await chatService.getUnreadMessageCount();
      messagingStore.setUnreadCount(unreadCount);
    } catch (error) {
      console.error('Chat initialization failed:', error);
      // Non-critical - app can function without chat
    }
  }
};
 
// Register task
initializer.registerTask(InitStage.FINALIZATION, chatInitTask);

Managing UI Transitions

Implement smooth transitions for the final UI state:

// ui/components/InitializationTransition.tsx
import React, { useEffect, useState } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import { useInitialization } from '@/core/shared/app/initialization';
 
interface InitializationTransitionProps {
  children: React.ReactNode;
  loadingFallback: React.ReactNode;
}
 
export function InitializationTransition({ 
  children, 
  loadingFallback 
}: InitializationTransitionProps) {
  const { initialized } = useInitialization();
  const [showChildren, setShowChildren] = useState(false);
  const fadeAnim = useState(new Animated.Value(0))[0];
  
  useEffect(() => {
    if (initialized) {
      // Start fade-in animation
      Animated.timing(fadeAnim, {
        toValue: 1,
        duration: 300,
        useNativeDriver: true,
      }).start();
      
      // Set content to visible
      setShowChildren(true);
    }
  }, [initialized, fadeAnim]);
  
  return (
    <View style={styles.container}>
      {!initialized && (
        <View style={styles.loadingContainer}>
          {loadingFallback}
        </View>
      )}
      
      {showChildren && (
        <Animated.View
          style={[
            styles.contentContainer,
            { opacity: fadeAnim }
          ]}
        >
          {children}
        </Animated.View>
      )}
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  loadingContainer: {
    ...StyleSheet.absoluteFillObject,
    zIndex: 10,
  },
  contentContainer: {
    flex: 1,
  },
});

Common Challenges

Dealing with Incomplete Background Tasks

Handle cases where background tasks haven't completed:

// core/shared/app/initialization/timeout-handler.ts
import { initializer, InitStage, InitTask } from './initializer';
import { initEvents } from './events';
 
// Force finalization if background tasks take too long
const forceFinalizationTask: InitTask = {
  name: 'system:force-finalization',
  
  async execute(): Promise<void> {
    // Set a maximum time for the Background stage
    const MAX_BACKGROUND_TIME = 15000; // 15 seconds
    
    return new Promise(resolve => {
      // Start a timer for the maximum background time
      const timer = setTimeout(() => {
        console.warn('Background initialization timed out, forcing finalization');
        
        // Get incomplete background tasks
        const pendingTasks = initializer.getPendingTasks(InitStage.BACKGROUND);
        
        if (pendingTasks.length > 0) {
          console.warn(`Incomplete background tasks: ${pendingTasks.map(t => t.name).join(', ')}`);
          
          // Log to analytics
          analyticsService.trackEvent('initialization_timeout', {
            stage: InitStage.BACKGROUND,
            pendingTasks: pendingTasks.map(t => t.name),
          });
          
          // Emit event for incomplete tasks
          initEvents.emit('stage:incomplete', {
            stage: InitStage.BACKGROUND,
            pendingTasks: pendingTasks.map(t => t.name),
          });
        }
        
        // Proceed to finalization anyway
        resolve();
      }, MAX_BACKGROUND_TIME);
      
      // Clean up timer if background stage completes normally
      initEvents.once('stage:complete', (stage) => {
        if (stage === InitStage.BACKGROUND) {
          clearTimeout(timer);
          resolve();
        }
      });
    });
  }
};
 
// Register task in the Background stage
initializer.registerTask(InitStage.BACKGROUND, {
  ...forceFinalizationTask,
  priority: 'low',
});

Managing UI State for Interrupted Initialization

Handle cases where initialization is interrupted:

// ui/components/InterruptibleInitialization.tsx
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { useInitialization } from '@/core/shared/app/initialization';
 
export function InterruptibleInitialization({ children }) {
  const { 
    initialized, 
    error, 
    currentStage, 
    incompleteStages 
  } = useInitialization();
  
  const [showOverride, setShowOverride] = useState(false);
  
  // Show override option if initialization takes too long
  useEffect(() => {
    if (!initialized && !error) {
      const timer = setTimeout(() => {
        setShowOverride(true);
      }, 30000); // 30 seconds
      
      return () => clearTimeout(timer);
    }
  }, [initialized, error]);
  
  // If initialization completed normally, show children
  if (initialized) {
    return <>{children}</>;
  }
  
  // If initialization failed with error
  if (error) {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>Something went wrong</Text>
        <Text style={styles.message}>
          We couldn't finish setting up the app. Please try again.
        </Text>
        <TouchableOpacity
          style={styles.button}
          onPress={() => initializer.restart()}
        >
          <Text style={styles.buttonText}>Retry</Text>
        </TouchableOpacity>
      </View>
    );
  }
  
  // If initialization is taking too long
  if (showOverride) {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>Taking longer than expected</Text>
        <Text style={styles.message}>
          {`Setup is still in progress (${currentStage}). `}
          You can continue with limited functionality or wait.
        </Text>
        
        <View style={styles.buttonRow}>
          <TouchableOpacity
            style={[styles.button, styles.secondaryButton]}
            onPress={() => {
              // Force initialization to complete
              initializer.forceComplete();
            }}
          >
            <Text style={styles.secondaryButtonText}>Continue Anyway</Text>
          </TouchableOpacity>
          
          <TouchableOpacity
            style={styles.button}
            onPress={() => {
              // Restart initialization
              initializer.restart();
            }}
          >
            <Text style={styles.buttonText}>Restart Setup</Text>
          </TouchableOpacity>
        </View>
      </View>
    );
  }
  
  // Normal loading state
  return <LoadingScreen stage={currentStage} />;
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 12,
  },
  message: {
    fontSize: 16,
    textAlign: 'center',
    marginBottom: 24,
    color: '#555',
  },
  buttonRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    width: '100%',
    paddingHorizontal: 20,
  },
  button: {
    backgroundColor: '#007AFF',
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    minWidth: 140,
    alignItems: 'center',
  },
  secondaryButton: {
    backgroundColor: 'transparent',
    borderWidth: 1,
    borderColor: '#007AFF',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
  secondaryButtonText: {
    color: '#007AFF',
    fontSize: 16,
    fontWeight: '600',
  },
});

Reporting Initialization Status

Provide a comprehensive initialization status report:

// core/shared/app/initialization/status-report.ts
import { initializer, InitStage, InitTask } from './initializer';
import { storage } from '@/core/shared/utils/storage';
 
// Generate and store initialization status report
const statusReportTask: InitTask = {
  name: 'metrics:status-report',
  stage: InitStage.FINALIZATION,
  priority: 'low',
  
  async execute(): Promise<void> {
    try {
      // Get initialization status
      const status = initializer.getStatus();
      
      // Create detailed report
      const report = {
        timestamp: Date.now(),
        success: status.completed,
        error: status.error ? {
          message: status.error.message,
          task: status.errorTask
        } : null,
        stages: {
          [InitStage.PRE_UI]: {
            completed: status.stageStatus[InitStage.PRE_UI].completed,
            duration: status.stageStatus[InitStage.PRE_UI].duration,
            tasks: status.stageStatus[InitStage.PRE_UI].tasks.map(t => ({
              name: t.name,
              success: t.completed,
              duration: t.duration,
              error: t.error ? t.error.message : null
            }))
          },
          [InitStage.INITIAL_UI]: {
            completed: status.stageStatus[InitStage.INITIAL_UI].completed,
            duration: status.stageStatus[InitStage.INITIAL_UI].duration,
            tasks: status.stageStatus[InitStage.INITIAL_UI].tasks.map(t => ({
              name: t.name,
              success: t.completed,
              duration: t.duration,
              error: t.error ? t.error.message : null
            }))
          },
          [InitStage.BACKGROUND]: {
            completed: status.stageStatus[InitStage.BACKGROUND].completed,
            duration: status.stageStatus[InitStage.BACKGROUND].duration,
            tasks: status.stageStatus[InitStage.BACKGROUND].tasks.map(t => ({
              name: t.name,
              success: t.completed,
              duration: t.duration,
              error: t.error ? t.error.message : null
            }))
          },
          [InitStage.FINALIZATION]: {
            completed: status.stageStatus[InitStage.FINALIZATION].completed,
            duration: status.stageStatus[InitStage.FINALIZATION].duration,
            tasks: status.stageStatus[InitStage.FINALIZATION].tasks.map(t => ({
              name: t.name,
              success: t.completed,
              duration: t.duration,
              error: t.error ? t.error.message : null
            }))
          }
        },
        environment: {
          appVersion: DeviceInfo.getVersion(),
          buildNumber: DeviceInfo.getBuildNumber(),
          deviceModel: DeviceInfo.getModel(),
          osVersion: DeviceInfo.getSystemVersion(),
          isEmulator: DeviceInfo.isEmulator(),
          timeZone: DeviceInfo.getTimezone(),
          totalMemory: DeviceInfo.getTotalMemory(),
          totalDiskCapacity: DeviceInfo.getTotalDiskCapacity()
        }
      };
      
      // Store report for debug purposes
      await storage.set('app:last-init-report', JSON.stringify(report));
      
      // In development mode, log report
      if (__DEV__) {
        console.log('Initialization status report:', report);
      }
      
      // If initialization failed, send report to monitoring service
      if (!status.completed || status.error) {
        monitoringService.reportInitializationFailure(report);
      }
    } catch (error) {
      console.error('Status report generation failed:', error);
      // Non-critical - app can function without status report
    }
  }
};
 
// Register task
initializer.registerTask(InitStage.FINALIZATION, statusReportTask);

Performance Considerations

  • (Do ✅) Keep finalization tasks lightweight

    • Focus on UI transitions and state updates
    • Defer any heavy operations to later user interactions
    • Prioritize a smooth transition to interactive state
  • (Do ✅) Provide visual feedback during transition

    • Use animations to mask any remaining work
    • Transition gradually from loading to final UI
    • Ensure all loading indicators are removed
  • (Don't ❌) Start heavyweight services immediately

    • Defer intensive monitoring until after user interactions begin
    • Use a phased approach for enabling features
    • Consider idle-time initialization for non-critical services
  • (Do ✅) Measure and report performance

    • Track time in each initialization stage
    • Record task-specific performance metrics
    • Identify optimization opportunities

Practical Examples

Transitioning from Skeleton Screens to Content

// features/home/screens/HomeScreen.tsx
import React, { useEffect, useState } from 'react';
import { View, StyleSheet, Animated } from 'react-native';
import { useInitialization } from '@/core/shared/app/initialization';
import { SkeletonContent } from '@/ui/components/SkeletonContent';
import { FeaturedProducts } from '../components/FeaturedProducts';
import { CategoryList } from '../components/CategoryList';
import { RecentlyViewed } from '../components/RecentlyViewed';
import { useHomeData } from '@/core/domains/home/hooks';
 
export function HomeScreen() {
  const { initialized } = useInitialization();
  const { data: homeData, isLoading } = useHomeData();
  
  const [contentVisible, setContentVisible] = useState(false);
  const fadeAnim = useState(new Animated.Value(0))[0];
  
  // Transition from skeletons to real content
  useEffect(() => {
    if (initialized && !isLoading && homeData) {
      // Short delay to ensure smooth transition
      setTimeout(() => {
        setContentVisible(true);
        
        Animated.timing(fadeAnim, {
          toValue: 1,
          duration: 300,
          useNativeDriver: true,
        }).start();
      }, 150);
    }
  }, [initialized, isLoading, homeData, fadeAnim]);
  
  return (
    <View style={styles.container}>
      {(!contentVisible) ? (
        <SkeletonContent
          containerStyle={styles.skeletonContainer}
          isLoading={true}
          layout={[
            { key: 'banner', width: '100%', height: 200, marginBottom: 20 },
            { key: 'categories', width: '100%', height: 100, marginBottom: 20 },
            { key: 'product1', width: '48%', height: 200, marginBottom: 12 },
            { key: 'product2', width: '48%', height: 200, marginBottom: 12 },
            { key: 'section', width: '80%', height: 30, marginBottom: 10 },
            { key: 'recent1', width: '100%', height: 80, marginBottom: 10 },
            { key: 'recent2', width: '100%', height: 80, marginBottom: 10 },
          ]}
        />
      ) : (
        <Animated.ScrollView
          style={[styles.contentContainer, { opacity: fadeAnim }]}
          contentContainerStyle={styles.scrollContent}
        >
          <FeaturedProducts items={homeData.featured} />
          <CategoryList categories={homeData.categories} />
          <RecentlyViewed items={homeData.recentlyViewed} />
        </Animated.ScrollView>
      )}
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  skeletonContainer: {
    flex: 1,
    padding: 16,
  },
  contentContainer: {
    flex: 1,
  },
  scrollContent: {
    padding: 16,
  },
});

Performance Report Dashboard

// features/developer/screens/PerformanceScreen.tsx
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { storage } from '@/core/shared/utils/storage';
import { Card } from '@/ui/components/Card';
import { BarChart } from '@/ui/components/charts/BarChart';
import { Section } from '@/ui/components/Section';
import { List } from '@/ui/components/List';
import { useNavigation } from '@react-navigation/native';
 
export function PerformanceScreen() {
  const [report, setReport] = useState(null);
  const navigation = useNavigation();
  
  // Load performance report
  useEffect(() => {
    const loadReport = async () => {
      try {
        const reportJson = await storage.get('app:last-init-report');
        if (reportJson) {
          setReport(JSON.parse(reportJson));
        }
      } catch (error) {
        console.error('Failed to load performance report:', error);
      }
    };
    
    loadReport();
  }, []);
  
  if (!report) {
    return (
      <View style={styles.container}>
        <Text style={styles.title}>No performance data available</Text>
      </View>
    );
  }
  
  // Prepare chart data for stage durations
  const stageDurations = [
    { name: 'Pre-UI', value: report.stages[InitStage.PRE_UI].duration },
    { name: 'Initial UI', value: report.stages[InitStage.INITIAL_UI].duration },
    { name: 'Background', value: report.stages[InitStage.BACKGROUND].duration },
    { name: 'Finalization', value: report.stages[InitStage.FINALIZATION].duration },
  ];
  
  // Get the 5 slowest tasks
  const slowestTasks = Object.values(report.stages)
    .flatMap(stage => stage.tasks)
    .sort((a, b) => b.duration - a.duration)
    .slice(0, 5)
    .map(task => ({
      name: task.name,
      value: task.duration,
    }));
  
  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>Initialization Performance</Text>
      
      <Card style={styles.card}>
        <Text style={styles.cardTitle}>Initialize in</Text>
        <Text style={styles.timeValue}>
          {`${Math.round(
            report.stages[InitStage.PRE_UI].duration +
            report.stages[InitStage.INITIAL_UI].duration +
            report.stages[InitStage.BACKGROUND].duration +
            report.stages[InitStage.FINALIZATION].duration
          )}ms`}
        </Text>
        <Text style={styles.timeSubtitle}>Total time</Text>
        
        <Text style={styles.metric}>
          {`${Math.round(
            report.stages[InitStage.PRE_UI].duration +
            report.stages[InitStage.INITIAL_UI].duration
          )}ms`}
        </Text>
        <Text style={styles.metricLabel}>Time to Interactive</Text>
      </Card>
      
      <Section title="Stage Durations">
        <BarChart
          data={stageDurations}
          style={styles.chart}
          height={200}
          xAxisLabel="Stage"
          yAxisLabel="Duration (ms)"
        />
      </Section>
      
      <Section title="Slowest Tasks">
        <BarChart
          data={slowestTasks}
          style={styles.chart}
          height={250}
          xAxisLabel="Task"
          yAxisLabel="Duration (ms)"
        />
      </Section>
      
      <Section title="Task Details">
        <List
          data={Object.values(report.stages)
            .flatMap(stage => stage.tasks)
            .sort((a, b) => b.duration - a.duration)}
          renderItem={({ item }) => (
            <View style={styles.taskItem}>
              <Text style={styles.taskName}>{item.name}</Text>
              <Text style={styles.taskDuration}>{`${item.duration}ms`}</Text>
              {item.error && (
                <Text style={styles.taskError}>{item.error}</Text>
              )}
            </View>
          )}
          keyExtractor={item => item.name}
        />
      </Section>
      
      <TouchableOpacity
        style={styles.button}
        onPress={() => {
          // Clear all initialization data and restart app
          storage.remove('app:last-init-report');
          
          Alert.alert(
            'Restart Required',
            'The app needs to restart to clear initialization data.',
            [
              {
                text: 'Cancel',
                style: 'cancel',
              },
              {
                text: 'Restart Now',
                onPress: () => {
                  RNRestart.Restart();
                },
              },
            ]
          );
        }}
      >
        <Text style={styles.buttonText}>Clear & Restart App</Text>
      </TouchableOpacity>
    </ScrollView>
  );
}
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#f8f8f8',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 16,
  },
  card: {
    padding: 16,
    marginBottom: 24,
    borderRadius: 8,
  },
  cardTitle: {
    fontSize: 16,
    color: '#777',
    marginBottom: 8,
  },
  timeValue: {
    fontSize: 36,
    fontWeight: 'bold',
    color: '#333',
  },
  timeSubtitle: {
    fontSize: 14,
    color: '#777',
    marginBottom: 16,
  },
  metric: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#333',
  },
  metricLabel: {
    fontSize: 14,
    color: '#777',
  },
  chart: {
    marginVertical: 16,
  },
  taskItem: {
    padding: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  taskName: {
    fontSize: 16,
    fontWeight: '500',
  },
  taskDuration: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
  taskError: {
    fontSize: 14,
    color: '#d00',
    marginTop: 4,
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginVertical: 24,
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
});

Migration Considerations

When migrating from traditional initialization completion approaches to our Finalization stage, consider these helpful strategies:

From Simple Loading States to Polished Transitions

Many applications use simple loading indicators that abruptly disappear when initialization completes:

// Traditional approach
const App = () => {
  const [isInitialized, setIsInitialized] = useState(false);
  
  useEffect(() => {
    async function initialize() {
      try {
        // Perform all initialization
        await setupServices();
        await loadData();
        
        // Simply set state to initialized when complete
        setIsInitialized(true);
      } catch (error) {
        console.error('Initialization failed', error);
        setIsInitialized(true); // Still mark as initialized to prevent app from being stuck
      }
    }
    initialize();
  }, []);
  
  if (!isInitialized) {
    return <LoadingScreen />;
  }
  
  return <MainApp />;
};

You can enhance this with the Finalization stage pattern:

  1. Add smooth transitions for removing loading indicators
  2. Collect performance metrics before completing initialization
  3. Enable deferred interactions that were disabled during initialization
  4. Start monitoring services only after the app is fully interactive

Enhancing User Experience During Transitions

A key improvement comes from implementing smoother transitions:

// Finalization-aware component with smooth transitions
const AppWithFinalization = () => {
  const { initialized, currentStage } = useInitialization();
  const [fadeAnim] = useState(new Animated.Value(1));
  
  // Use effects to manage transitions based on initialization stage
  useEffect(() => {
    if (currentStage === InitStage.FINALIZATION) {
      // Register finalization tasks
      initializer.registerTask(InitStage.FINALIZATION, {
        name: 'ui:polish',
        execute: async () => {
          // Smooth fade-out loading overlay
          Animated.timing(fadeAnim, {
            toValue: 0,
            duration: 500,
            useNativeDriver: true,
          }).start();
          
          // Wait for animation to complete
          await new Promise(resolve => setTimeout(resolve, 500));
          
          // Log performance metrics
          analyticsService.logEvent('app_init_complete', {
            timeToInteractive: performance.now()
          });
        }
      });
    }
  }, [currentStage, fadeAnim]);
  
  return (
    <View style={{ flex: 1 }}>
      <MainApp />
      
      {!initialized && (
        <Animated.View 
          style={[
            styles.loadingOverlay,
            { opacity: fadeAnim }
          ]}
        >
          <LoadingIndicator stage={currentStage} />
        </Animated.View>
      )}
    </View>
  );
};

Implementing Phased Migration

You can migrate to the Finalization stage pattern incrementally:

  1. Start by simply wrapping your current loading removal logic in a Finalization task
  2. Add performance measurement to collect initialization metrics
  3. Gradually enhance the transitions between loading and fully interactive states
  4. Implement deferred feature activation for non-critical functionality

The Finalization stage allows you to create a professional, polished transition from initialization to full app functionality, significantly improving perceived performance and user experience.

Summary

The Finalization stage completes the application initialization process that began with the pre-ui, continued through the initial-ui and background, ensuring a smooth transition from loading to fully interactive state. By carefully implementing this stage, you can create a polished user experience that feels responsive and professional.

Key principles to remember:

  • Remove all loading indicators and show the final UI state
  • Enable all deferred interactions and features
  • Collect and report performance metrics
  • Start monitoring and background services
  • Perform cleanup and optimization

By following these patterns, you'll ensure that your application completes its initialization process smoothly and provides users with a fully functional, responsive experience from the very beginning of their session.

Core Initialization Documentation

Other Initialization Stages

  • monitoring - Monitoring app performance after initialization
  • analytics - Setting up analytics during finalization
  • animations - Creating smooth transitions between loading states