or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

app-utilities.mderror-handling.mdevent-communication.mdindex.mdnative-modules.mdnative-views.mdpermissions.mdplatform-utilities.mdshared-memory.mduuid-generation.md
tile.json

permissions.mddocs/

Permission Management

React Hook-based permission system with standardized request/response patterns and automatic lifecycle management.

Capabilities

Permission Status and Response Types

Core types for representing permission states and responses.

/**
 * Enumeration of possible permission states
 */
enum PermissionStatus {
  /** User has granted the permission */
  GRANTED = 'granted',
  /** User hasn't granted or denied the permission yet */
  UNDETERMINED = 'undetermined',
  /** User has denied the permission */
  DENIED = 'denied',
}

/**
 * Permission expiration time - currently all permissions are permanent
 */
type PermissionExpiration = 'never' | number;

/**
 * Standard permission response object returned by get and request functions
 */
interface PermissionResponse {
  /** Current permission status */
  status: PermissionStatus;
  /** When the permission expires */
  expires: PermissionExpiration;
  /** Convenience boolean indicating if permission is granted */
  granted: boolean;
  /** Whether user can be asked again or should be directed to Settings */
  canAskAgain: boolean;
}

Usage Examples:

import { PermissionStatus, type PermissionResponse } from "expo-modules-core";

// Check permission status
function handlePermissionResponse(response: PermissionResponse) {
  switch (response.status) {
    case PermissionStatus.GRANTED:
      console.log("Permission granted!");
      // Proceed with protected functionality
      break;
      
    case PermissionStatus.DENIED:
      if (response.canAskAgain) {
        console.log("Permission denied, but can ask again");
        // Show explanation and request again
      } else {
        console.log("Permission permanently denied, direct to settings");
        // Guide user to system settings
      }
      break;
      
    case PermissionStatus.UNDETERMINED:
      console.log("Permission not yet requested");
      // First time, can request permission
      break;
  }
  
  // Use convenience property
  if (response.granted) {
    enableFeature();
  } else {
    showPermissionRequiredMessage();
  }
}

Permission Hook Creation

Factory function for creating permission hooks with standardized patterns.

/**
 * Creates a new permission hook with built-in permission methods
 * @param methods - Object containing request and get methods
 * @returns Hook function for managing permissions
 */
function createPermissionHook<Permission extends PermissionResponse, Options extends object>(
  methods: PermissionHookMethods<Permission, Options>
): (options?: PermissionHookOptions<Options>) => 
  [Permission | null, () => Promise<Permission>, () => Promise<Permission>];

/**
 * Permission methods required for creating a hook
 */
interface PermissionHookMethods<Permission extends PermissionResponse, Options = never> {
  /** Method that requests permission from user */
  requestMethod: (options?: Options) => Promise<Permission>;
  /** Method that only fetches current permission status */
  getMethod: (options?: Options) => Promise<Permission>;
}

/**
 * Options for controlling permission hook behavior
 */
interface PermissionHookOptions<Options extends object> extends Options {
  /** Automatically fetch permission status on mount */
  get?: boolean;
  /** Automatically request permission on mount */
  request?: boolean;
}

Usage Examples:

import { createPermissionHook, PermissionResponse, PermissionStatus } from "expo-modules-core";

// Define permission-specific response type
interface CameraPermissionResponse extends PermissionResponse {
  canAskAgain: boolean;
  // Additional camera-specific properties
}

// Define options for camera permission
interface CameraPermissionOptions {
  allowsEditing?: boolean;
  aspect?: [number, number];
}

// Create camera permission methods
const cameraPermissionMethods = {
  async requestMethod(options?: CameraPermissionOptions): Promise<CameraPermissionResponse> {
    // Implementation would call native camera permission request
    const nativeResponse = await requestCameraPermissionNative(options);
    return {
      status: nativeResponse.granted ? PermissionStatus.GRANTED : PermissionStatus.DENIED,
      expires: 'never',
      granted: nativeResponse.granted,
      canAskAgain: nativeResponse.canAskAgain,
    };
  },
  
  async getMethod(options?: CameraPermissionOptions): Promise<CameraPermissionResponse> {
    // Implementation would check current camera permission status
    const nativeResponse = await getCameraPermissionNative();
    return {
      status: nativeResponse.status,
      expires: 'never',
      granted: nativeResponse.granted,
      canAskAgain: nativeResponse.canAskAgain,
    };
  },
};

// Create the hook
const useCameraPermissions = createPermissionHook(cameraPermissionMethods);

// Use in component
function CameraComponent() {
  // Hook returns [status, requestPermission, getPermission]
  const [permission, requestPermission, getPermission] = useCameraPermissions({
    get: true, // Automatically fetch status on mount
    allowsEditing: true, // Custom option
  });
  
  const handleCameraPress = async () => {
    if (!permission?.granted) {
      const newPermission = await requestPermission();
      if (!newPermission.granted) {
        alert("Camera permission required");
        return;
      }
    }
    
    // Open camera
    openCamera();
  };
  
  if (permission === null) {
    return <div>Loading permissions...</div>;
  }
  
  return (
    <button onClick={handleCameraPress} disabled={!permission.granted}>
      {permission.granted ? "Open Camera" : "Grant Camera Permission"}
    </button>
  );
}

Advanced Permission Hook Usage

Examples of complex permission scenarios and patterns.

import { createPermissionHook, PermissionResponse } from "expo-modules-core";

// Location permission with multiple precision levels
interface LocationPermissionResponse extends PermissionResponse {
  accuracy: 'coarse' | 'fine' | 'none';
}

interface LocationPermissionOptions {
  accuracy?: 'coarse' | 'fine';
  backgroundLocation?: boolean;
}

const useLocationPermissions = createPermissionHook({
  async requestMethod(options?: LocationPermissionOptions): Promise<LocationPermissionResponse> {
    // Request location permission with specified accuracy
    const result = await requestLocationPermission(options);
    return result;
  },
  
  async getMethod(options?: LocationPermissionOptions): Promise<LocationPermissionResponse> {
    // Get current location permission status
    const result = await getLocationPermission();
    return result;
  },
});

// Multi-permission component
function LocationAwareComponent() {
  const [locationPermission, requestLocation] = useLocationPermissions({
    get: true,
    accuracy: 'fine',
  });
  
  const [backgroundPermission, requestBackground] = useLocationPermissions({
    backgroundLocation: true,
  });
  
  const handleEnableLocation = async () => {
    // Request foreground location first
    const foreground = await requestLocation();
    
    if (foreground.granted && foreground.accuracy === 'fine') {
      // Then request background location
      const background = await requestBackground();
      
      if (background.granted) {
        console.log("Both permissions granted");
        enableBackgroundTracking();
      }
    }
  };
  
  return (
    <div>
      <p>Foreground Location: {locationPermission?.status}</p>
      <p>Background Location: {backgroundPermission?.status}</p>
      <button onClick={handleEnableLocation}>
        Enable Location Tracking
      </button>
    </div>
  );
}

// Permission wrapper component
function withPermissions<T extends object>(
  WrappedComponent: React.ComponentType<T>,
  requiredPermissions: string[]
) {
  return function PermissionWrapper(props: T) {
    const [allGranted, setAllGranted] = useState(false);
    
    useEffect(() => {
      // Check all required permissions
      Promise.all(
        requiredPermissions.map(checkPermission)
      ).then(results => {
        setAllGranted(results.every(r => r.granted));
      });
    }, []);
    
    if (!allGranted) {
      return <PermissionGate permissions={requiredPermissions} />;
    }
    
    return <WrappedComponent {...props} />;
  };
}

// Usage
const LocationEnabledMap = withPermissions(MapComponent, ['location']);

Permission Management Best Practices

Guidelines for effective permission handling:

import { createPermissionHook, PermissionStatus } from "expo-modules-core";

// ✅ Good: Explain why permission is needed
function PermissionExplanation({ onGrant }: { onGrant: () => void }) {
  const [permission, requestPermission] = useCameraPermissions();
  
  const handleRequest = async () => {
    if (permission?.status === PermissionStatus.DENIED && !permission.canAskAgain) {
      // Guide to settings
      alert("Please enable camera access in Settings");
      return;
    }
    
    const result = await requestPermission();
    if (result.granted) {
      onGrant();
    }
  };
  
  return (
    <div>
      <h3>Camera Access Required</h3>
      <p>This app needs camera access to take photos for your profile.</p>
      <button onClick={handleRequest}>Grant Camera Access</button>
    </div>
  );
}

// ✅ Good: Handle all permission states
function PermissionAwareFeature() {
  const [permission, requestPermission] = useLocationPermissions({ get: true });
  
  const renderPermissionState = () => {
    if (!permission) {
      return <div>Checking permissions...</div>;
    }
    
    switch (permission.status) {
      case PermissionStatus.GRANTED:
        return <LocationFeature />;
        
      case PermissionStatus.UNDETERMINED:
        return (
          <button onClick={requestPermission}>
            Enable Location Services
          </button>
        );
        
      case PermissionStatus.DENIED:
        return permission.canAskAgain ? (
          <div>
            <p>Location access is needed for this feature.</p>
            <button onClick={requestPermission}>Try Again</button>
          </div>
        ) : (
          <div>
            <p>Please enable location access in Settings</p>
            <button onClick={openSettings}>Open Settings</button>
          </div>
        );
        
      default:
        return <div>Unknown permission state</div>;
    }
  };
  
  return <div>{renderPermissionState()}</div>;
}

// ❌ Bad: Not handling denied permissions
function BadPermissionComponent() {
  const [permission, requestPermission] = useCameraPermissions();
  
  // Only checks granted, ignores denied/undetermined
  return permission?.granted ? (
    <CameraView />
  ) : (
    <button onClick={requestPermission}>Enable Camera</button>
  );
}

Types

/**
 * Generic permission hook return type
 */
type PermissionHookReturn<Permission extends PermissionResponse> = [
  Permission | null,              // Current permission status
  () => Promise<Permission>,      // Request permission function
  () => Promise<Permission>       // Get current permission function
];

/**
 * Base permission response interface
 */
interface PermissionResponse {
  status: PermissionStatus;
  expires: PermissionExpiration;
  granted: boolean;
  canAskAgain: boolean;
}