rn-native-features

Native iOS features in Expo React Native apps. Use when implementing camera, push notifications, haptics, permissions, device sensors, or other native APIs in Expo.

242 stars

Best use case

rn-native-features is best used when you need a repeatable AI agent workflow instead of a one-off prompt. It is especially useful for teams working in multi. Native iOS features in Expo React Native apps. Use when implementing camera, push notifications, haptics, permissions, device sensors, or other native APIs in Expo.

Native iOS features in Expo React Native apps. Use when implementing camera, push notifications, haptics, permissions, device sensors, or other native APIs in Expo.

Users should expect a more consistent workflow output, faster repeated execution, and less time spent rewriting prompts from scratch.

Practical example

Example input

Use the "rn-native-features" skill to help with this workflow task. Context: Native iOS features in Expo React Native apps. Use when implementing camera, push notifications, haptics, permissions, device sensors, or other native APIs in Expo.

Example output

A structured workflow result with clearer steps, more consistent formatting, and an output that is easier to reuse in the next run.

When to use this skill

  • Use this skill when you want a reusable workflow rather than writing the same prompt again and again.

When not to use this skill

  • Do not use this when you only need a one-off answer and do not need a reusable workflow.
  • Do not use it if you cannot install or maintain the related files, repository context, or supporting tools.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/rn-native-features/SKILL.md --create-dirs "https://raw.githubusercontent.com/aiskillstore/marketplace/main/skills/cjharmath/rn-native-features/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/rn-native-features/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How rn-native-features Compares

Feature / Agentrn-native-featuresStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Native iOS features in Expo React Native apps. Use when implementing camera, push notifications, haptics, permissions, device sensors, or other native APIs in Expo.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Native Features (Expo)

## Permissions Pattern

Always request permissions before using native features:

```typescript
import * as Camera from 'expo-camera';

async function requestCameraPermission() {
  const { status } = await Camera.requestCameraPermissionsAsync();
  
  if (status !== 'granted') {
    // Guide user to settings if denied
    Alert.alert(
      'Camera Access Required',
      'Please enable camera access in Settings to take photos.',
      [
        { text: 'Cancel', style: 'cancel' },
        { text: 'Open Settings', onPress: () => Linking.openSettings() },
      ]
    );
    return false;
  }
  return true;
}
```

### Check permission status first

```typescript
import * as Camera from 'expo-camera';

function usePermission() {
  const [permission, requestPermission] = Camera.useCameraPermissions();
  
  // permission.granted - boolean
  // permission.canAskAgain - false if user selected "Don't ask again"
  // permission.status - 'granted' | 'denied' | 'undetermined'
  
  return { permission, requestPermission };
}
```

## Camera

### Basic Camera with Photo Capture

```typescript
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useRef, useState } from 'react';

export function CameraScreen() {
  const [permission, requestPermission] = useCameraPermissions();
  const [facing, setFacing] = useState<'front' | 'back'>('back');
  const cameraRef = useRef<CameraView>(null);

  if (!permission) return <View />;
  
  if (!permission.granted) {
    return (
      <View>
        <Text>Camera access is required</Text>
        <Button title="Grant Permission" onPress={requestPermission} />
      </View>
    );
  }

  const takePicture = async () => {
    if (cameraRef.current) {
      const photo = await cameraRef.current.takePictureAsync({
        quality: 0.8,
        base64: false,
        exif: false,
      });
      console.log('Photo URI:', photo.uri);
      // photo.uri is a local file path
    }
  };

  return (
    <View style={{ flex: 1 }}>
      <CameraView 
        ref={cameraRef}
        style={{ flex: 1 }} 
        facing={facing}
      >
        <View style={styles.controls}>
          <Button title="Flip" onPress={() => setFacing(f => f === 'back' ? 'front' : 'back')} />
          <Button title="Take Photo" onPress={takePicture} />
        </View>
      </CameraView>
    </View>
  );
}
```

### Image Picker (Gallery + Camera)

Often simpler than full camera UI:

```typescript
import * as ImagePicker from 'expo-image-picker';

async function pickImage() {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [1, 1],
    quality: 0.8,
  });

  if (!result.canceled) {
    return result.assets[0].uri;
  }
}

async function takePhoto() {
  const permission = await ImagePicker.requestCameraPermissionsAsync();
  if (!permission.granted) return;
  
  const result = await ImagePicker.launchCameraAsync({
    allowsEditing: true,
    aspect: [1, 1],
    quality: 0.8,
  });

  if (!result.canceled) {
    return result.assets[0].uri;
  }
}
```

## Push Notifications

### Setup with expo-notifications

```typescript
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { Platform } from 'react-native';

// Configure how notifications appear when app is foregrounded
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: true,
  }),
});

async function registerForPushNotifications() {
  if (!Device.isDevice) {
    console.log('Push notifications require a physical device');
    return null;
  }

  // Check existing permission
  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  let finalStatus = existingStatus;

  // Request if not determined
  if (existingStatus !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }

  if (finalStatus !== 'granted') {
    console.log('Push notification permission denied');
    return null;
  }

  // Get Expo push token
  const token = await Notifications.getExpoPushTokenAsync({
    projectId: 'your-expo-project-id', // From app.json
  });

  return token.data; // "ExponentPushToken[xxxx]"
}
```

### Handle incoming notifications

```typescript
import { useEffect, useRef } from 'react';
import * as Notifications from 'expo-notifications';

export function useNotificationHandler() {
  const notificationListener = useRef<Notifications.Subscription>();
  const responseListener = useRef<Notifications.Subscription>();

  useEffect(() => {
    // Notification received while app is foregrounded
    notificationListener.current = Notifications.addNotificationReceivedListener(
      (notification) => {
        console.log('Received:', notification.request.content);
      }
    );

    // User tapped on notification
    responseListener.current = Notifications.addNotificationResponseReceivedListener(
      (response) => {
        const data = response.notification.request.content.data;
        // Navigate based on notification data
        if (data.screen) {
          router.push(data.screen);
        }
      }
    );

    return () => {
      notificationListener.current?.remove();
      responseListener.current?.remove();
    };
  }, []);
}
```

### Send test notification locally

```typescript
async function sendTestNotification() {
  await Notifications.scheduleNotificationAsync({
    content: {
      title: "Match Reminder",
      body: "Your match starts in 30 minutes!",
      data: { screen: '/match/123' },
    },
    trigger: { seconds: 5 },
  });
}
```

### Send from backend (Expo Push API)

```python
# FastAPI example
import httpx

async def send_push_notification(
    expo_push_token: str, 
    title: str, 
    body: str, 
    data: dict = None
):
    message = {
        "to": expo_push_token,
        "title": title,
        "body": body,
        "data": data or {},
    }
    
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://exp.host/--/api/v2/push/send",
            json=message,
            headers={"Content-Type": "application/json"},
        )
        return response.json()
```

## Haptic Feedback

```typescript
import * as Haptics from 'expo-haptics';

// Light tap - for UI interactions
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);

// Medium - for confirmations
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);

// Heavy - for significant actions
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);

// Success/Error/Warning - semantic feedback
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);

// Selection - for picker/scroll
Haptics.selectionAsync();
```

### Good haptic patterns

```typescript
// Button press
<Pressable 
  onPress={() => {
    Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
    handlePress();
  }}
/>

// Form submission success
await submitForm();
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);

// Error state
if (error) {
  Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
}

// Picker/Scroll selection
<Picker 
  onValueChange={(value) => {
    Haptics.selectionAsync();
    setValue(value);
  }}
/>
```

## Location

```typescript
import * as Location from 'expo-location';

async function getCurrentLocation() {
  const { status } = await Location.requestForegroundPermissionsAsync();
  if (status !== 'granted') {
    return null;
  }

  const location = await Location.getCurrentPositionAsync({
    accuracy: Location.Accuracy.Balanced,
  });
  
  return {
    latitude: location.coords.latitude,
    longitude: location.coords.longitude,
  };
}

// Watch location changes
function useLocationTracking() {
  const [location, setLocation] = useState(null);

  useEffect(() => {
    let subscription: Location.LocationSubscription;
    
    (async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') return;
      
      subscription = await Location.watchPositionAsync(
        {
          accuracy: Location.Accuracy.High,
          distanceInterval: 10, // meters
        },
        (loc) => setLocation(loc.coords)
      );
    })();
    
    return () => subscription?.remove();
  }, []);
  
  return location;
}
```

## Local Storage

### AsyncStorage (simple key-value)

```typescript
import AsyncStorage from '@react-native-async-storage/async-storage';

// Store
await AsyncStorage.setItem('user_preferences', JSON.stringify(prefs));

// Retrieve
const prefs = JSON.parse(await AsyncStorage.getItem('user_preferences') || '{}');

// Remove
await AsyncStorage.removeItem('user_preferences');
```

### SecureStore (sensitive data)

```typescript
import * as SecureStore from 'expo-secure-store';

// For tokens, credentials - encrypted storage
await SecureStore.setItemAsync('auth_token', token);
const token = await SecureStore.getItemAsync('auth_token');
await SecureStore.deleteItemAsync('auth_token');
```

## App State & Lifecycle

```typescript
import { useEffect } from 'react';
import { AppState, AppStateStatus } from 'react-native';

function useAppState(callback: (state: AppStateStatus) => void) {
  useEffect(() => {
    const subscription = AppState.addEventListener('change', callback);
    return () => subscription.remove();
  }, [callback]);
}

// Usage
useAppState((state) => {
  if (state === 'active') {
    // App came to foreground - refresh data
    refreshData();
  } else if (state === 'background') {
    // App going to background - save state
    saveState();
  }
});
```

## Keyboard Handling

```typescript
import { KeyboardAvoidingView, Platform } from 'react-native';

// Wrap forms to avoid keyboard
<KeyboardAvoidingView 
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
  style={{ flex: 1 }}
>
  <YourForm />
</KeyboardAvoidingView>

// Dismiss keyboard
import { Keyboard } from 'react-native';
Keyboard.dismiss();

// Listen to keyboard events
import { useEffect } from 'react';
import { Keyboard } from 'react-native';

function useKeyboardVisible() {
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const showSub = Keyboard.addListener('keyboardDidShow', () => setVisible(true));
    const hideSub = Keyboard.addListener('keyboardDidHide', () => setVisible(false));
    return () => {
      showSub.remove();
      hideSub.remove();
    };
  }, []);

  return visible;
}
```

## Common Issues

| Issue | Solution |
|-------|----------|
| Camera black screen | Check permissions, ensure CameraView has explicit dimensions |
| Notifications not received | Verify physical device, check push token registration |
| Location inaccurate | Use `Accuracy.High`, check device location services enabled |
| Haptics not working | Only works on physical device, not simulator |
| SecureStore size limit | Max ~2KB per item, use for tokens not large data |

Related Skills

react-native-design

242
from aiskillstore/marketplace

Master React Native styling, navigation, and Reanimated animations for cross-platform mobile development. Use when building React Native apps, implementing navigation patterns, or creating performant animations.

game-changing-features

242
from aiskillstore/marketplace

Find 10x product opportunities and high-leverage improvements. Use when user wants strategic product thinking, mentions '10x', wants to find high-impact features, or says 'what would make this 10x better', 'product strategy', or 'what should we build next'.

react-native-architecture

242
from aiskillstore/marketplace

Build production React Native apps with Expo, navigation, native modules, offline sync, and cross-platform patterns. Use when developing mobile apps, implementing native integrations, or architecting React Native projects.

react-native-dev

242
from aiskillstore/marketplace

React Native and Expo development guide covering components, styling, animations, navigation, state management, forms, networking, performance optimization, testing, native capabilities, and engineering (project structure, deployment, SDK upgrades, CI/CD). Use when: building React Native or Expo apps, implementing animations or native UI, managing state, fetching data, writing tests, optimizing performance, deploying to App Store/Play Store, setting up CI/CD, upgrading Expo SDK, or configuring Tailwind/NativeWind.

android-native-dev

242
from aiskillstore/marketplace

Android native application development and UI design guide. Covers Material Design 3, Kotlin/Compose development, project configuration, accessibility, and build troubleshooting. Read this before Android native application development.

firebase-firestore-enterprise-native-mode

242
from aiskillstore/marketplace

Comprehensive guide for Firestore enterprise native including provisioning, data model, security rules, and SDK usage. Use this skill when the user needs help setting up Firestore Enterprise with the Native mode, writing security rules, or using the Firestore SDK in their application.

native-data-fetching

242
from aiskillstore/marketplace

Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, axios, React Query, SWR, error handling, caching strategies, offline support.

building-native-ui

242
from aiskillstore/marketplace

Complete guide for building beautiful apps with Expo Router. Covers fundamentals, styling, components, navigation, animations, patterns, and native tabs.

brainstorming-features

242
from aiskillstore/marketplace

Facilitates creative ideation sessions for mobile and web app features, generating structured ideas with user stories, technical considerations, and implementation suggestions. Use when planning new features, exploring product direction, generating app ideas, feature discovery, product brainstorming, or when user mentions 'brainstorm', 'ideate', 'app ideas', or 'feature suggestions'.

agentdb-advanced-features

242
from aiskillstore/marketplace

Master advanced AgentDB features including QUIC synchronization, multi-database management, custom distance metrics, hybrid search, and distributed systems integration. Use when building distributed AI systems, multi-agent coordination, or advanced vector search applications.

competitor-alternatives

242
from aiskillstore/marketplace

When the user wants to create competitor comparison or alternative pages for SEO and sales enablement. Also use when the user mentions 'alternative page,' 'vs page,' 'competitor comparison,' 'comparison page,' '[Product] vs [Product],' '[Product] alternative,' or 'competitive landing pages.' Covers four formats: singular alternative, plural alternatives, you vs competitor, and competitor vs competitor. Emphasizes deep research, modular content architecture, and varied section types beyond feature tables.

orchestration-native-invoke

242
from aiskillstore/marketplace

Invoke external AI CLIs via native Task agents (Claude, Codex, Gemini, Cursor). Primary mode for multi-provider orchestration with fork-terminal fallback for auth.