Skip to content

Import Export Usage Guide

Masked-Kunsiquat edited this page Dec 25, 2025 · 2 revisions

Import/Export Usage Guide

This guide shows developers how to integrate the Import/Export module into CrewSplit screens and workflows.

Quick Start

1. Add Route File (Expo Router v6)

Create a route file for the import/export screen:

// app/import-export.tsx
import { ImportExportScreen } from "@modules/import-export";

export default ImportExportScreen;

export const options = {
  title: "Import & Export",
  headerShown: true,
};

2. Navigate to Screen

import { useRouter } from 'expo-router';
import { Link } from 'expo-router';

// Option 1: Programmatic navigation
function SettingsMenu() {
  const router = useRouter();

  return (
    <Button
      title="Import/Export"
      onPress={() => router.push('/import-export')}
    />
  );
}

// Option 2: Link component
function SettingsMenu() {
  return (
    <Link href="/import-export" asChild>
      <Button title="Import/Export" />
    </Link>
  );
}

UI Components

ImportExportScreen

Full-featured screen for import/export operations.

Features:

  • Export Current Trip (when tripId param provided)
  • Export Full Database
  • Import from File
  • Configurable options (sample data, archived data)
  • Loading states and error handling

Usage with Trip Context:

function TripDashboard({ tripId }: { tripId: string }) {
  const router = useRouter();

  return (
    <Button
      title="Export Trip"
      onPress={() => router.push({
        pathname: '/import-export',
        params: { tripId },
      })}
    />
  );
}

SettingsExportSection

Compact component for Settings screens.

Features:

  • Backup Database button
  • Restore from Backup button
  • Informational tip
  • Optional callbacks

Basic Usage:

import { SettingsExportSection } from '@modules/import-export';

function SettingsScreen() {
  return (
    <ScrollView>
      {/* Other settings sections */}
      <SettingsExportSection />
    </ScrollView>
  );
}

With Callbacks:

function SettingsScreen() {
  const handleExportComplete = () => {
    console.log('Backup created');
    // Could refresh UI, update badge, etc.
  };

  const handleImportComplete = () => {
    console.log('Data restored');
    Alert.alert(
      'Restart Required',
      'Please restart the app for changes to take effect.'
    );
  };

  return (
    <ScrollView>
      <SettingsExportSection
        onExportComplete={handleExportComplete}
        onImportComplete={handleImportComplete}
      />
    </ScrollView>
  );
}

React Hooks

useExport Hook

import { useExport } from '@modules/import-export';

function CustomExportButton({ tripId }: { tripId: string }) {
  const {
    exportTrip,
    exportFullDatabase,
    isExporting,
    error,
  } = useExport();

  const handleExport = async () => {
    await exportTrip(tripId, {
      includeSampleData: false,
      includeArchivedData: false,
    });
  };

  return (
    <Button
      title={isExporting ? "Exporting..." : "Export Trip"}
      onPress={handleExport}
      disabled={isExporting}
    />
  );
}

API:

interface UseExportReturn {
  exportTrip: (tripId: string, options?: ExportOptions) => Promise<void>;
  exportFullDatabase: (options?: ExportOptions) => Promise<void>;
  isExporting: boolean;
  error: Error | null;
}

interface ExportOptions {
  includeSampleData?: boolean;
  includeArchivedData?: boolean;
}

useImport Hook

import { useImport } from '@modules/import-export';

function CustomImportButton() {
  const {
    importFromFile,
    previewImport,
    isImporting,
    error,
    result,
  } = useImport();

  const handleImport = async () => {
    await importFromFile('skip');  // or 'replace'
  };

  return (
    <>
      <Button
        title={isImporting ? "Importing..." : "Import Data"}
        onPress={handleImport}
        disabled={isImporting}
      />

      {result && (
        <Text>
          Imported: {result.successCount}, Skipped: {result.skippedCount}
        </Text>
      )}
    </>
  );
}

API:

interface UseImportReturn {
  importFromFile: (
    conflictResolution?: ConflictStrategy,
    options?: ImportOptions,
  ) => Promise<void>;

  previewImport: (file: any) => Promise<ImportPreview>;
  isImporting: boolean;
  error: Error | null;
  result: ImportResult[] | null;
}

type ConflictStrategy = "skip" | "replace" | "generate_new_ids";

interface ImportOptions {
  validateForeignKeys?: boolean;
  dryRun?: boolean;
}

Service Layer Usage

For advanced use cases, use the services directly:

ExportService

import { ExportService } from "@modules/import-export";

const exportService = new ExportService();

// Export single trip
const tripData = await exportService.exportTrip(tripId, {
  includeSampleData: false,
  includeArchivedData: false,
});

// Export full database
const fullData = await exportService.exportFullDatabase({
  includeSampleData: true,
  includeArchivedData: true,
});

// Export global data only (categories, FX rates)
const globalData = await exportService.exportGlobalData({
  includeArchivedData: false,
});

// Write to file and share
await exportService.writeToFile(tripData, "trip-backup.json");

ImportService

import { ImportService } from "@modules/import-export";

const importService = new ImportService();

// Import from file (opens file picker)
const results = await importService.importFromFile("skip", {
  validateForeignKeys: true,
  dryRun: false,
});

// Preview import conflicts
const preview = await importService.previewImport(exportData);
console.log(
  `${preview.totalRecords} records, ${preview.totalConflicts} conflicts`,
);

// Import from parsed data
const results = await importService.importFromData(exportData, "replace");

// Get import summary
const summary = importService.getImportSummary(results);
console.log(`Success: ${summary.successCount}, Errors: ${summary.errorCount}`);

User Flows

Flow 1: Backup Data

  1. User navigates to Settings
  2. Taps "Backup Database" in SettingsExportSection
  3. Button shows loading ("Creating Backup...")
  4. Native share dialog appears with backup file
  5. User saves/shares file
  6. Success alert confirms backup created
  7. Optional onExportComplete callback fires

Flow 2: Share Trip

  1. User viewing trip dashboard
  2. Taps "Export" or "Share Trip"
  3. ImportExportScreen opens with trip context
  4. User adjusts options (exclude sample data, etc.)
  5. Taps "Export Trip"
  6. Share dialog appears
  7. User sends via email/messaging
  8. Success alert confirms

Flow 3: Restore from Backup

  1. User navigates to Import/Export screen
  2. Reads warning about duplicate handling
  3. Taps "Choose File"
  4. File picker opens
  5. Selects backup JSON file
  6. Import processes with loading overlay
  7. Success alert shows summary (X imported, Y skipped)
  8. Optional onImportComplete callback fires

Error Handling

The hooks handle all errors automatically with user-facing alerts:

Export Errors:

  • File system access denied
  • Insufficient storage
  • Invalid data format

Import Errors:

  • Invalid JSON structure
  • Unsupported file version
  • Missing required fields
  • Foreign key violations
  • Database constraint errors

Example Error Display:

Import Failed

Invalid export file structure:
- Missing required field: version
- Invalid trip ID format in record 3
- Foreign key violation: participant 'abc-123' not found

Please check the file and try again.

Complete Example: Settings Screen

import React from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { theme } from '@ui/theme';
import { Card } from '@ui/components';
import { SettingsExportSection } from '@modules/import-export';
import { useRouter } from 'expo-router';

export default function SettingsScreen() {
  const router = useRouter();

  const handleExportComplete = () => {
    console.log('Backup created successfully');
  };

  const handleImportComplete = () => {
    console.log('Data restored successfully');
    // Could show restart prompt, clear caches, etc.
  };

  return (
    <View style={theme.commonStyles.container}>
      <ScrollView
        style={styles.scrollView}
        contentContainerStyle={styles.content}
      >
        {/* App Info */}
        <Card>
          <Text style={styles.sectionTitle}>About</Text>
          <View style={styles.row}>
            <Text style={styles.label}>Version</Text>
            <Text style={styles.value}>1.1.0</Text>
          </View>
        </Card>

        {/* Backup & Restore */}
        <SettingsExportSection
          onExportComplete={handleExportComplete}
          onImportComplete={handleImportComplete}
        />

        {/* Advanced Options */}
        <Card>
          <Text style={styles.sectionTitle}>Advanced</Text>
          <Button
            title="Advanced Import/Export"
            onPress={() => router.push('/import-export')}
          />
        </Card>
      </ScrollView>
    </View>
  );
}

const styles = StyleSheet.create({
  scrollView: {
    flex: 1,
  },
  content: {
    padding: theme.spacing.lg,
    gap: theme.spacing.lg,
  },
  sectionTitle: {
    fontSize: theme.typography.lg,
    fontWeight: theme.typography.bold,
    color: theme.colors.text,
    marginBottom: theme.spacing.md,
  },
  row: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    paddingVertical: theme.spacing.sm,
  },
  label: {
    fontSize: theme.typography.base,
    color: theme.colors.textSecondary,
  },
  value: {
    fontSize: theme.typography.base,
    color: theme.colors.text,
  },
});

Testing Recommendations

Key flows to test:

  1. Export trip: Verify file created and shareable
  2. Export full DB: Verify all data included
  3. Toggle options: Ensure options affect export content
  4. Import valid file: Verify data imported correctly
  5. Import duplicates: Verify skip strategy works
  6. Import invalid file: Verify error shown gracefully
  7. Cancel operations: Ensure no crashes on cancel
  8. Loading states: Verify buttons disable during operations
  9. Success feedback: Verify alerts appear
  10. Screen reader: Test with TalkBack/VoiceOver

Accessibility

Both screens follow accessibility best practices:

  • All buttons have accessibilityLabel and accessibilityHint
  • Touch targets meet 44x44pt minimum
  • Screen reader support with meaningful labels
  • Loading states announced to assistive tech
  • High contrast colors for visibility

Related Documentation

Clone this wiki locally