Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
thebushidocollective avatar

Expo Modules

  • 1 installs
  • 186 repo stars
  • Updated July 19, 2026
  • thebushidocollective/han

Work with Expo SDK modules for camera, location, notifications, and file system, covering permissions and configuration.

About

Covers Expo SDK modules for device APIs like camera, location, notifications, and file system including permissions and best practices. A developer uses it when adding native device features to an Expo app.

  • Camera, location, notifications, file system modules
  • Permission handling and configuration

Expo Modules by the numbers

  • 1 all-time installs (skills.sh)
  • Ranked #959 of 1,039 Mobile Development skills by installs in the Skillselion catalog
  • Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thebushidocollective/han --skill expo-modules

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1
repo stars186
Last updatedJuly 19, 2026
Repositorythebushidocollective/han

What it does

Work with Expo SDK modules for camera, location, notifications, and file system, covering permissions and configuration.

Files

SKILL.mdMarkdownGitHub ↗

Expo Modules

Use this skill when working with Expo's extensive SDK modules for accessing device features and native functionality.

Key Concepts

Camera

import { Camera, CameraType } from 'expo-camera';
import { useState } from 'react';
import { Button, View } from 'react-native';

export default function CameraScreen() {
  const [permission, requestPermission] = Camera.useCameraPermissions();
  const [type, setType] = useState(CameraType.back);

  if (!permission?.granted) {
    return (
      <View>
        <Button title="Grant Permission" onPress={requestPermission} />
      </View>
    );
  }

  return (
    <Camera style={{ flex: 1 }} type={type}>
      <Button
        title="Flip Camera"
        onPress={() =>
          setType(type === CameraType.back ? CameraType.front : CameraType.back)
        }
      />
    </Camera>
  );
}

Location

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

export function useLocation() {
  const [location, setLocation] = useState<Location.LocationObject | null>(null);

  useEffect(() => {
    (async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') return;

      const loc = await Location.getCurrentPositionAsync({});
      setLocation(loc);
    })();
  }, []);

  return location;
}

Notifications

import * as Notifications from 'expo-notifications';
import { useEffect } from 'react';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

export function useNotifications() {
  useEffect(() => {
    const subscription = Notifications.addNotificationReceivedListener(
      (notification) => {
        console.log(notification);
      }
    );

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

  const sendNotification = async () => {
    await Notifications.scheduleNotificationAsync({
      content: {
        title: 'Hello!',
        body: 'This is a notification',
      },
      trigger: { seconds: 2 },
    });
  };

  return { sendNotification };
}

File System

import * as FileSystem from 'expo-file-system';

export async function saveFile(data: string, filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  await FileSystem.writeAsStringAsync(uri, data);
  return uri;
}

export async function readFile(filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  const content = await FileSystem.readAsStringAsync(uri);
  return content;
}

export async function downloadFile(url: string, filename: string) {
  const uri = `${FileSystem.documentDirectory}${filename}`;
  const download = await FileSystem.downloadAsync(url, uri);
  return download.uri;
}

Image Picker

import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import { Button, Image, View } from 'react-native';

export default function ImagePickerScreen() {
  const [image, setImage] = useState<string | null>(null);

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

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

  return (
    <View>
      <Button title="Pick Image" onPress={pickImage} />
      {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
    </View>
  );
}

Best Practices

Permission Handling

import * as Location from 'expo-location';

async function requestLocationPermission() {
  const { status: foregroundStatus } =
    await Location.requestForegroundPermissionsAsync();

  if (foregroundStatus !== 'granted') {
    console.log('Permission denied');
    return false;
  }

  // Request background permission only if needed
  const { status: backgroundStatus } =
    await Location.requestBackgroundPermissionsAsync();

  return backgroundStatus === 'granted';
}

Secure Storage

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

export async function saveToken(key: string, value: string) {
  await SecureStore.setItemAsync(key, value);
}

export async function getToken(key: string) {
  return await SecureStore.getItemAsync(key);
}

export async function deleteToken(key: string) {
  await SecureStore.deleteItemAsync(key);
}

Device Info

import * as Device from 'expo-device';
import * as Application from 'expo-application';
import Constants from 'expo-constants';

export function getDeviceInfo() {
  return {
    deviceName: Device.deviceName,
    deviceType: Device.deviceType,
    osName: Device.osName,
    osVersion: Device.osVersion,
    appVersion: Application.nativeApplicationVersion,
    buildVersion: Application.nativeBuildVersion,
    expoVersion: Constants.expoVersion,
  };
}

Common Patterns

Persistent Storage

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

export const storage = {
  async set(key: string, value: any) {
    await AsyncStorage.setItem(key, JSON.stringify(value));
  },

  async get<T>(key: string): Promise<T | null> {
    const item = await AsyncStorage.getItem(key);
    return item ? JSON.parse(item) : null;
  },

  async remove(key: string) {
    await AsyncStorage.removeItem(key);
  },

  async clear() {
    await AsyncStorage.clear();
  },
};

Background Tasks

import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';

const BACKGROUND_FETCH_TASK = 'background-fetch';

TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
  // Do work here
  console.log('Background task running');
  return BackgroundFetch.BackgroundFetchResult.NewData;
});

export async function registerBackgroundTask() {
  return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
    minimumInterval: 60 * 15, // 15 minutes
    stopOnTerminate: false,
    startOnBoot: true,
  });
}

Sharing Content

import * as Sharing from 'expo-sharing';

export async function shareContent(uri: string) {
  const isAvailable = await Sharing.isAvailableAsync();

  if (!isAvailable) {
    console.log('Sharing is not available');
    return;
  }

  await Sharing.shareAsync(uri, {
    mimeType: 'image/jpeg',
    dialogTitle: 'Share this image',
  });
}

Related Skills

  • expo-config: Configuring module permissions
  • expo-build: Including modules in builds

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.