Teardown

Advanced Usage

Advanced patterns and configurations for the Teardown SDK.

Multiple Environments

Use the environment_slug prop to target different environments within a single project:

const getEnvironment = () => __DEV__ ? 'development' : 'production';

export const teardown = new TeardownCore({
  org_id: 'your-org-id',
  project_id: 'your-project-id',
  api_key: 'your-api-key',
  environment_slug: getEnvironment(),
  storageAdapter: new MMKVStorageAdapter(),
  deviceAdapter: new ExpoDeviceAdapter(),
});

Environments are configured in your project's dashboard settings. Common slugs include development, staging, and production (default).

Push Notifications Setup

Enable push notifications by providing a notification adapter:

import { TeardownCore } from '@teardown/force-updates';
import { ExpoNotificationsAdapter, ExpoDeviceAdapter } from '@teardown/force-updates/expo';
import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv';

const teardown = new TeardownCore({
  org_id: 'your-org-id',
  project_id: 'your-project-id',
  api_key: 'your-api-key',
  storageAdapter: new MMKVStorageAdapter(),
  deviceAdapter: new ExpoDeviceAdapter(),
  notificationAdapter: new ExpoNotificationsAdapter(), // Enable notifications
});

// Request permissions and get token
if (teardown.notifications) {
  const status = await teardown.notifications.requestPermissions();
  if (status.granted) {
    const token = await teardown.notifications.getToken();
    // Token is automatically sent with identify() calls
  }
}

Handle Notifications

const { core } = useTeardown();

useEffect(() => {
  if (!core.notifications) return;

  const unsubs = [
    core.notifications.onNotificationReceived((notification) => {
      // Foreground notification
      console.log('Notification:', notification.title);
    }),
    core.notifications.onNotificationOpened((notification) => {
      // User tapped notification
      navigateToScreen(notification.data?.screen);
    }),
    core.notifications.onDataMessage((message) => {
      // Silent/data message
      syncData(message.data);
    }),
  ];

  return () => unsubs.forEach(unsub => unsub());
}, [core.notifications]);

Event Tracking

Track custom events:

const { core } = useTeardown();

// Track single event
await core.events.track({
  event_name: 'purchase_completed',
  event_type: 'action',
  properties: {
    product_id: 'abc123',
    amount: 99.99,
    currency: 'USD',
  },
});

// Track multiple events
await core.events.trackBatch([
  { event_name: 'cart_viewed', event_type: 'screen_view' },
  { event_name: 'item_added', properties: { item_id: 'xyz' } },
]);

Disable Force Update Checking

For debugging or testing:

const teardown = new TeardownCore({
  // ...
  forceUpdate: {
    checkIntervalMs: -1, // Disable automatic checking
  },
});

Custom Version Check Interval

Check more or less frequently:

const teardown = new TeardownCore({
  // ...
  forceUpdate: {
    checkIntervalMs: 60_000,     // Check every minute
    checkOnForeground: true,
  },
});

Manual Version Checking

Trigger version check programmatically:

const { core } = useTeardown();

const checkForUpdates = async () => {
  const result = await core.identity.refresh();
  if (result.success) {
    // Version status will update via subscription
    console.log('Version checked');
  }
};

Offline Support

The SDK handles offline scenarios gracefully:

  1. Cached session - Identity state persists across restarts
  2. Cached version status - Last known status is preserved
  3. Retry on foreground - Re-checks when connectivity restored
const session = useSession();

// Works offline - returns cached session
if (session) {
  console.log('User:', session.user_id);
}

Pre-warming

Initialize SDK before app is fully loaded:

// In app entry point (before React renders)
import { teardown } from './lib/teardown';

// SDK starts initializing immediately
// By the time UI renders, it may already be ready

Error Boundaries

Handle SDK errors gracefully:

import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    <ErrorBoundary fallback={<ErrorScreen />}>
      <TeardownProvider core={teardown}>
        <MainApp />
      </TeardownProvider>
    </ErrorBoundary>
  );
}

Testing

Mock the SDK

// __mocks__/@teardown/force-updates.ts
export const TeardownProvider = ({ children }) => children;

export const useTeardown = () => ({
  core: {
    identity: {
      identify: jest.fn().mockResolvedValue({ success: true, data: mockSession }),
      signOut: jest.fn().mockResolvedValue({ success: true, data: undefined }),
      signOutAll: jest.fn().mockResolvedValue({ success: true, data: undefined }),
      getSessionState: () => mockSession,
    },
    forceUpdate: {
      getVersionStatus: () => ({ type: 'up_to_date' }),
    },
    events: {
      track: jest.fn().mockResolvedValue({ success: true, data: undefined }),
      trackBatch: jest.fn().mockResolvedValue({ success: true, data: undefined }),
    },
    notifications: {
      requestPermissions: jest.fn().mockResolvedValue({ granted: true, canAskAgain: true }),
      getToken: jest.fn().mockResolvedValue('mock-push-token'),
      onNotificationReceived: jest.fn().mockReturnValue(() => {}),
      onNotificationOpened: jest.fn().mockReturnValue(() => {}),
      onDataMessage: jest.fn().mockReturnValue(() => {}),
    },
  },
});

export const useSession = () => mockSession;
export const useForceUpdate = () => ({
  versionStatus: { type: 'up_to_date' },
  isUpdateRequired: false,
  isUpdateRecommended: false,
  isUpdateAvailable: false,
  releaseNotes: null,
});

Test Components

import { render } from '@testing-library/react-native';

test('shows login when not identified', () => {
  jest.spyOn(hooks, 'useSession').mockReturnValue(null);

  const { getByText } = render(<App />);
  expect(getByText('Login')).toBeTruthy();
});

Debugging

Enable Verbose Logging

teardown.setLogLevel('verbose');

Inspect State

const { core } = useTeardown();

console.log('Identity state:', core.identity.getIdentifyState());
console.log('Version status:', core.forceUpdate.getVersionStatus());
console.log('Device ID:', await core.device.getDeviceId());

Clear All Data

For debugging or logout:

// Sign out - clears session, keeps device ID
await core.identity.signOut();

// Full reset - clears session AND device ID (fresh install state)
await core.identity.signOutAll();

Performance

Minimize Re-renders

Use specific hooks instead of broad subscriptions:

// Good - only re-renders on session change
const session = useSession();

// Avoid - accessing core gives no reactivity
const { core } = useTeardown();
const session = core.identity.getSessionState(); // Not reactive!

Lazy Initialization

Defer SDK init until needed:

let teardownInstance: TeardownCore | null = null;

export const getTeardown = () => {
  if (!teardownInstance) {
    teardownInstance = new TeardownCore({ /* ... */ });
  }
  return teardownInstance;
};

Cleanup

Properly cleanup on app shutdown:

import { AppState } from 'react-native';

useEffect(() => {
  const subscription = AppState.addEventListener('change', (state) => {
    if (state === 'background') {
      // SDK persists state automatically, no action needed
    }
  });

  return () => {
    subscription.remove();
    teardown.shutdown();
  };
}, []);

TypeScript

Strict Types

All SDK types are exported:

import type {
  TeardownCore,
  TeardownCoreOptions,
  Session,
  IdentifyState,
  VersionStatus,
  Persona,
  AsyncResult,
} from '@teardown/force-updates';

Type Guards

function handleIdentifyState(state: IdentifyState) {
  switch (state.type) {
    case 'unidentified':
      // state is UnidentifiedSessionState
      break;
    case 'identifying':
      // state is IdentifyingSessionState
      break;
    case 'identified':
      // state is IdentifiedSessionState
      console.log(state.session.user_id);
      break;
  }
}