Awesome-omni-skill mobile

React Native, Expo, mobile apps. Auto-use for mobile work.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/fullstack-web/mobile" ~/.claude/skills/diegosouzapw-awesome-omni-skill-mobile-46e91d && rm -rf "$T"
manifest: skills/fullstack-web/mobile/SKILL.md
source content

Mobile

Auto-use when: Expo, React Native, mobile, app, NativeWind, iOS, Android

Works with:

backend
for API,
quality
for testing


Quick Setup

File Structure

app/
├── _layout.tsx        # Root (providers, fonts)
├── index.tsx          # Home
├── (auth)/            # Auth group
│   ├── _layout.tsx
│   └── login.tsx
├── (tabs)/            # Tab group (authenticated)
│   ├── _layout.tsx
│   ├── index.tsx
│   └── profile.tsx
└── [id].tsx           # Dynamic route

Root Layout

// app/_layout.tsx
import { Stack } from 'expo-router'
import { QueryClientProvider } from '@tanstack/react-query'
import '../global.css'

export default function RootLayout() {
  return (
    <QueryClientProvider client={queryClient}>
      <AuthProvider>
        <Stack screenOptions={{ headerShown: false }} />
      </AuthProvider>
    </QueryClientProvider>
  )
}

Protected Tabs

// app/(tabs)/_layout.tsx
import { Tabs, Redirect } from 'expo-router'
import { useAuth } from '@/providers/auth'

export default function TabLayout() {
  const { session, isLoading } = useAuth()

  if (isLoading) return null
  if (!session) return <Redirect href="/login" />

  return (
    <Tabs>
      <Tabs.Screen name="index" options={{ title: 'Home' }} />
      <Tabs.Screen name="profile" options={{ title: 'Profile' }} />
    </Tabs>
  )
}

NativeWind

// Usage (Tailwind syntax)
<View className="flex-1 bg-white p-4">
  <Text className="text-lg font-bold text-gray-900">Title</Text>
  <Pressable className="bg-blue-500 p-4 rounded-lg active:opacity-70">
    <Text className="text-white text-center">Button</Text>
  </Pressable>
</View>

// Platform-specific
<View className="ios:pt-12 android:pt-8" />

// Dark mode
<Text className="text-gray-900 dark:text-white" />

Supabase Mobile

Client

// lib/supabase.ts
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'

export const supabase = createClient(url, key, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,  // Important for mobile
  },
})

Realtime with Cleanup (CRITICAL)

useEffect(() => {
  let channel: RealtimeChannel

  const setup = async () => {
    const { data } = await supabase.from('items').select('*')
    setData(data || [])

    channel = supabase
      .channel(`room:${roomId}`)
      .on('postgres_changes', { event: '*', schema: 'public', table: 'items' },
        (payload) => setData(prev => [...prev, payload.new])
      )
      .subscribe()
  }

  setup()

  // CRITICAL: Cleanup
  return () => {
    if (channel) supabase.removeChannel(channel)
  }
}, [roomId])

Common Patterns

Navigation

import { router, useLocalSearchParams, Link } from 'expo-router'

router.push('/profile')
router.push({ pathname: '/user/[id]', params: { id: '123' } })
router.replace('/login')
router.back()

const { id } = useLocalSearchParams<{ id: string }>()

Platform-Specific

import { Platform } from 'react-native'

const padding = Platform.OS === 'ios' ? 50 : 30

const styles = Platform.select({
  ios: { shadowColor: '#000', shadowOpacity: 0.1 },
  android: { elevation: 4 },
})

Safe Area

import { useSafeAreaInsets } from 'react-native-safe-area-context'

const insets = useSafeAreaInsets()
<View style={{ paddingTop: insets.top }} />

// Or with NativeWind
<View className="pt-safe pb-safe" />

EAS Commands

# Development build
eas build --profile development --platform ios

# Preview (internal testing)
eas build --profile preview --platform all

# Production
eas build --profile production --platform all

# Submit to stores
eas submit --platform ios
eas submit --platform android

# OTA update
eas update --branch production --message "Bug fixes"

Package Installation

# ALWAYS use npx expo install (not npm install)
npx expo install package-name

# Common packages
npx expo install react-native-reanimated
npx expo install @react-native-async-storage/async-storage
npx expo install expo-image-picker
npx expo install expo-secure-store

Checklist

[] Uses REAL data (not mock)
[] Loading states
[] Error states
[] Empty states
[] Cleanup subscriptions on unmount
[] Use expo-secure-store for secrets
[] HTTPS for all API calls
[] Platform-specific handling where needed