Lesson 7 of 8

Native Modules & Platform APIs

Accessing device features, camera, location, notifications, and native code

Accessing Native Features

React Native provides access to native device features through built-in APIs and third-party libraries. Expo makes many features available out of the box, while React Native CLI requires installing native modules.

Camera Access

// Install: npx expo install expo-camera

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

function CameraScreen() {
  const [facing, setFacing] = useState<CameraType>('back');
  const [permission, requestPermission] = useCameraPermissions();
  const cameraRef = useRef<CameraView>(null);
  
  if (!permission) {
    return <View />;
  }
  
  if (!permission.granted) {
    return (
      <View style={styles.container}>
        <Text>We need camera permission</Text>
        <Button title="Grant Permission" onPress={requestPermission} />
      </View>
    );
  }
  
  const takePicture = async () => {
    if (cameraRef.current) {
      const photo = await cameraRef.current.takePictureAsync();
      console.log('Photo taken:', photo.uri);
    }
  };
  
  const toggleCameraFacing = () => {
    setFacing(current => current === 'back' ? 'front' : 'back');
  };
  
  return (
    <View style={styles.container}>
      <CameraView style={styles.camera} facing={facing} ref={cameraRef}>
        <View style={styles.buttonContainer}>
          <TouchableOpacity style={styles.button} onPress={toggleCameraFacing}>
            <Text style={styles.text}>Flip</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.captureButton} onPress={takePicture}>
            <View style={styles.captureInner} />
          </TouchableOpacity>
        </View>
      </CameraView>
    </View>
  );
}

Location Services

// Install: npx expo install expo-location

import * as Location from 'expo-location';
import { useState, useEffect } from 'react';

function LocationScreen() {
  const [location, setLocation] = useState<Location.LocationObject | null>(null);
  const [address, setAddress] = useState<string | null>(null);
  const [errorMsg, setErrorMsg] = useState<string | null>(null);
  
  useEffect(() => {
    (async () => {
      // Request permission
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        setErrorMsg('Permission denied');
        return;
      }
      
      // Get current location
      const location = await Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.High,
      });
      setLocation(location);
      
      // Reverse geocode to get address
      const [geocode] = await Location.reverseGeocodeAsync({
        latitude: location.coords.latitude,
        longitude: location.coords.longitude,
      });
      
      if (geocode) {
        setAddress(`${geocode.street}, ${geocode.city}, ${geocode.country}`);
      }
    })();
  }, []);
  
  // Watch position changes
  const startWatching = async () => {
    const subscription = await Location.watchPositionAsync(
      {
        accuracy: Location.Accuracy.High,
        timeInterval: 1000,
        distanceInterval: 10,
      },
      (newLocation) => {
        setLocation(newLocation);
      }
    );
    
    // Remember to remove subscription
    return () => subscription.remove();
  };
  
  return (
    <View style={styles.container}>
      {location && (
        <Text>
          Lat: {location.coords.latitude.toFixed(4)}
          Lng: {location.coords.longitude.toFixed(4)}
        </Text>
      )}
      {address && <Text>Address: {address}</Text>}
    </View>
  );
}

Push Notifications

// Install: npx expo install expo-notifications expo-device

import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { useEffect, useRef, useState } from 'react';

// Configure notification behavior
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: true,
  }),
});

function NotificationSetup() {
  const [expoPushToken, setExpoPushToken] = useState('');
  const notificationListener = useRef();
  const responseListener = useRef();
  
  useEffect(() => {
    // Register for push notifications
    registerForPushNotifications().then(token => {
      setExpoPushToken(token);
      // Send token to your server
    });
    
    // Listen for incoming notifications
    notificationListener.current = Notifications.addNotificationReceivedListener(
      notification => {
        console.log('Notification received:', notification);
      }
    );
    
    // Listen for user interaction with notification
    responseListener.current = Notifications.addNotificationResponseReceivedListener(
      response => {
        const { notification } = response;
        // Navigate to appropriate screen based on notification data
        console.log('User tapped notification:', notification.request.content.data);
      }
    );
    
    return () => {
      Notifications.removeNotificationSubscription(notificationListener.current);
      Notifications.removeNotificationSubscription(responseListener.current);
    };
  }, []);
  
  return null;
}

async function registerForPushNotifications() {
  if (!Device.isDevice) {
    alert('Must use physical device for Push Notifications');
    return;
  }
  
  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  let finalStatus = existingStatus;
  
  if (existingStatus !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }
  
  if (finalStatus !== 'granted') {
    return;
  }
  
  const token = await Notifications.getExpoPushTokenAsync({
    projectId: 'your-project-id', // From app.json
  });
  
  return token.data;
}

// Schedule a local notification
async function scheduleNotification() {
  await Notifications.scheduleNotificationAsync({
    content: {
      title: 'Reminder',
      body: "Don't forget to check your tasks!",
      data: { screen: 'Tasks' },
    },
    trigger: { seconds: 60 }, // In 1 minute
  });
}

Image Picker

// Install: npx expo install expo-image-picker

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

function ImagePickerScreen() {
  const [image, setImage] = useState<string | null>(null);
  
  const pickImage = async () => {
    // Request permission
    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
    
    if (status !== 'granted') {
      alert('Sorry, we need camera roll permissions!');
      return;
    }
    
    // Open image picker
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 0.8,
    });
    
    if (!result.canceled) {
      setImage(result.assets[0].uri);
      // Upload to server
      await uploadImage(result.assets[0].uri);
    }
  };
  
  const takePhoto = async () => {
    const { status } = await ImagePicker.requestCameraPermissionsAsync();
    
    if (status !== 'granted') {
      alert('Sorry, we need camera permissions!');
      return;
    }
    
    const result = await ImagePicker.launchCameraAsync({
      allowsEditing: true,
      aspect: [1, 1],
      quality: 0.8,
    });
    
    if (!result.canceled) {
      setImage(result.assets[0].uri);
    }
  };
  
  return (
    <View style={styles.container}>
      <Button title="Pick from Gallery" onPress={pickImage} />
      <Button title="Take Photo" onPress={takePhoto} />
      {image && <Image source={{ uri: image }} style={styles.image} />}
    </View>
  );
}

Biometric Authentication

// Install: npx expo install expo-local-authentication

import * as LocalAuthentication from 'expo-local-authentication';

async function authenticateUser() {
  // Check if hardware supports biometrics
  const compatible = await LocalAuthentication.hasHardwareAsync();
  
  if (!compatible) {
    alert('Biometric authentication not available');
    return false;
  }
  
  // Check what types are enrolled
  const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
  // Returns: [1] for fingerprint, [2] for face recognition
  
  // Check if biometrics are enrolled
  const enrolled = await LocalAuthentication.isEnrolledAsync();
  
  if (!enrolled) {
    alert('No biometrics enrolled on this device');
    return false;
  }
  
  // Authenticate
  const result = await LocalAuthentication.authenticateAsync({
    promptMessage: 'Authenticate to continue',
    cancelLabel: 'Cancel',
    fallbackLabel: 'Use passcode',
    disableDeviceFallback: false,
  });
  
  if (result.success) {
    console.log('Authentication successful!');
    return true;
  } else {
    console.log('Authentication failed:', result.error);
    return false;
  }
}

// Usage
function SecureScreen() {
  const [authenticated, setAuthenticated] = useState(false);
  
  useEffect(() => {
    authenticateUser().then(setAuthenticated);
  }, []);
  
  if (!authenticated) {
    return <Text>Please authenticate to view this content</Text>;
  }
  
  return <Text>Secure content here</Text>;
}

Haptic Feedback

// Install: npx expo install expo-haptics

import * as Haptics from 'expo-haptics';

function HapticButton({ onPress, title }) {
  const handlePress = () => {
    // Light impact
    Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
    onPress();
  };
  
  return <Button title={title} onPress={handlePress} />;
}

// Different haptic types
async function triggerHaptics() {
  // Impact feedback (Light, Medium, Heavy)
  await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
  
  // Notification feedback (Success, Warning, Error)
  await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
  
  // Selection feedback (for selection changes)
  await Haptics.selectionAsync();
}

Common Expo Modules Reference

Feature Package Use Case
Camera expo-camera Take photos/videos
Location expo-location GPS, geocoding
Notifications expo-notifications Push & local notifications
File System expo-file-system Read/write files
Sharing expo-sharing Share content
Clipboard expo-clipboard Copy/paste
Sensors expo-sensors Accelerometer, gyro
Secure Store expo-secure-store Encrypted storage