Mobile Developer
Expert in React Native, Expo, and cross-platform mobile development
Specification
- Skill ID
- daffy0208/ai-dev-standards/mobile-developer
- Publisher
- daffy0208
- Repository
- ai-dev-standards
- Installs
- 0
- Files
- 3
- Synced
- Sep 23, 2026
How to use it
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
daffy0208/ai-dev-standards/mobile-developerInstalls these files- README.md
- SKILL.md
- manifest.yaml
What this skill tells the agent
Mobile Developer Skill
I help you build cross-platform mobile apps with React Native and Expo.
What I Do
App Development:
- React Native / Expo apps (iOS + Android)
- Navigation and routing
- State management
- API integration
Native Features:
- Camera, location, notifications
- Biometric authentication
- File system access
- Device sensors
Performance:
- Optimize bundle size
- Lazy loading
- Image optimization
- Memory management
Distribution:
- App Store / Google Play submission
- Over-the-air (OTA) updates
- Beta testing (TestFlight, internal testing)
Quick Start: Expo App
Create New App
# Create Expo app
npx create-expo-app my-app --template blank-typescript
cd my-app
# Install dependencies
npx expo install react-native-screens react-native-safe-area-context
npx expo install expo-router
# Start development
npx expo startProject Structure
my-app/
├── app/
│ ├── (tabs)/
│ │ ├── index.tsx # Home tab
│ │ ├── profile.tsx # Profile tab
│ │ └── _layout.tsx # Tab layout
│ ├── users/
│ │ └── [id].tsx # Dynamic route
│ ├── _layout.tsx # Root layout
│ └── +not-found.tsx # 404 page
├── components/
│ ├── Button.tsx
│ ├── Card.tsx
│ └── Loading.tsx
├── hooks/
│ └── useAuth.ts
├── app.json
└── package.jsonNavigation with Expo Router
Tab Navigation
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#007AFF',
headerShown: false
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
)
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
)
}}
/>
</Tabs>
)
}Stack Navigation
// app/users/[id].tsx
import { useLocalSearchParams } from 'expo-router'
import { View, Text } from 'react-native'
export default function UserDetail() {
const { id } = useLocalSearchParams()
return (
<View>
<Text>User ID: {id}</Text>
</View>
)
}UI Components
Custom Button
// components/Button.tsx
import { TouchableOpacity, Text, StyleSheet, ActivityIndicator } from 'react-native'
interface ButtonProps {
title: string
onPress: () => void
variant?: 'primary' | 'secondary'
loading?: boolean
disabled?: boolean
}
export function Button({
title,
onPress,
variant = 'primary',
loading = false,
disabled = false
}: ButtonProps) {
return (
<TouchableOpacity
style={[
styles.button,
variant === 'primary' ? styles.primary : styles.secondary,
disabled && styles.disabled
]}
onPress={onPress}
disabled={disabled || loading}
activeOpacity={0.7}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.text}>{title}</Text>
)}
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
button: {
padding: 16,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center'
},
primary: {
backgroundColor: '#007AFF'
},
secondary: {
backgroundColor: '#8E8E93'
},
disabled: {
opacity: 0.5
},
text: {
color: '#fff',
fontSize: 16,
fontWeight: '600'
}
})Card Component
// components/Card.tsx
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'
import { ReactNode } from 'react'
interface CardProps {
title?: string
children: ReactNode
onPress?: () => void
}
export function Card({ title, children, onPress }: CardProps) {
const Container = onPress ? TouchableOpacity : View
return (
<Container
style={styles.card}
onPress={onPress}
activeOpacity={onPress ? 0.7 : 1}
>
{title && <Text style={styles.title}>{title}</Text>}
{children}
</Container>
)
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3
},
title: {
fontSize: 18,
fontWeight: '600',
marginBottom: 12
}
})Data Fetching
Custom Hook
// hooks/useQuery.ts
import { useState, useEffect } from 'react'
interface UseQueryResult<T> {
data: T | null
loading: boolean
error: Error | null
refetch: () => void
}
export function useQuery<T>(url: string): UseQueryResult<T> {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
const fetchData = async () => {
try {
setLoading(true)
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const json = await response.json()
setData(json)
setError(null)
} catch (e) {
setError(e as Error)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchData()
}, [url])
return { data, loading, error, refetch: fetchData }
}Usage
// app/(tabs)/index.tsx
import { View, Text, FlatList, RefreshControl } from 'react-native'
import { useQuery } from '@/hooks/useQuery'
import { Card } from '@/components/Card'
interface Post {
id: string
title: string
content: string
}
export default function HomeScreen() {
const { data, loading, error, refetch } = useQuery<Post[]>(
'https://api.example.com/posts'
)
if (error) {
return (
<View>
<Text>Error: {error.message}</Text>
</View>
)
}
return (
<FlatList
data={data || []}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Card title={item.title}>
<Text>{item.content}</Text>
</Card>
)}
refreshControl={
<RefreshControl refreshing={loading} onRefresh={refetch} />
}
/>
)
}Native Features
Camera
// app/camera.tsx
import { Camera, CameraType } from 'expo-camera'
import { useState } from 'react'
import { Button, View, StyleSheet } from 'react-native'
export default function CameraScreen() {
const [type, setType] = useState(CameraType.back)
const [permission, requestPermission] = Camera.useCameraPermissions()
if (!permission) {
return <View />
}
if (!permission.granted) {
return (
<View style={styles.container}>
<Button onPress={requestPermission} title="Grant Camera Permission" />
</View>
)
}
return (
<View style={styles.container}>
<Camera style={styles.camera} type={type}>
<View style={styles.buttonContainer}>
<Button
onPress={() => {
setType(current =>
current === CameraType.back
? CameraType.front
: CameraType.back
)
}}
title="Flip Camera"
/>
</View>
</Camera>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1 },
camera: { flex: 1 },
buttonContainer: {
flex: 1,
backgroundColor: 'transparent',
justifyContent: 'flex-end',
padding: 20
}
})Push Notifications
// hooks/useNotifications.ts
import { useState, useEffect, useRef } from 'react'
import * as Notifications from 'expo-notifications'
import * as Device from 'expo-device'
import { Platform } from 'react-native'
More from daffy0208
Use this skill in a project
Open any RiverX project, search the Skills panel for daffy0208/ai-dev-standards/mobile-developer, and install it in one click.
