Agent skill
mobile-engineer
Expert in mobile app architecture using Expo, React Native, TypeScript, and Zustand. Use when building mobile screens, implementing navigation, managing state, styling components, integrating with backend APIs, or handling platform-specific functionality.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/mobile-engineer-ugudlado-react-expo-api-templ-2
SKILL.md
Mobile Engineer
Expert knowledge of mobile patterns and architecture for Expo React Native apps.
Core Architecture
Technology Stack
- Framework: Expo SDK 54 with React Native
- Routing: Expo Router (file-based routing)
- State Management: Zustand with AsyncStorage persistence
- Styling: React Native StyleSheet
- Icons: lucide-react-native
- Storage: @react-native-async-storage/async-storage
Data Flow Architecture
Screen (React Native)
|
State Decision:
├── Server Data -> Zustand Store -> API Service -> fetch
| |
| AsyncStorage (offline cache)
|
└── UI State -> Local useState
Key Flow:
- Screen component calls Zustand store
- Store fetches from API service
- Data cached in AsyncStorage for offline use
- Optimistic updates with rollback on error
Pattern References (ALWAYS CHECK THESE FIRST)
| Pattern | Reference Path | Notes |
|---|---|---|
| Screen Layout | app/(tabs)/index.tsx |
Main screen with list |
| Navigation Setup | app/_layout.tsx |
Stack/tab navigator config |
| Zustand Store | store/*.ts |
State with offline caching |
| API Service | services/*.ts |
Type-safe API calls |
| Components | components/ |
Reusable UI components |
| Theme | theme/colors.ts |
Color palette + tokens |
| Types | types/ |
Domain types |
Project Structure
app/ # Expo Router screens (file-based routing)
├── _layout.tsx # Root layout with navigation config
├── (tabs)/ # Tab navigation group
├── modal/ # Modal screens
└── [id].tsx # Dynamic routes
components/ # Reusable UI components
store/ # Zustand state management
services/ # API services
theme/ # Design system (colors, spacing, radii)
types/ # TypeScript types
assets/ # Images, icons, fonts
State Management with Zustand
Store Pattern with Offline Support
import { create } from 'zustand';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AppState {
items: Item[];
isLoading: boolean;
error: string | null;
fetchItems: () => Promise<void>;
addItem: (input: CreateItemInput) => Promise<void>;
}
const STORAGE_KEY = '@app/data';
export const useAppStore = create<AppState>((set, get) => ({
items: [],
isLoading: false,
error: null,
fetchItems: async () => {
set({ isLoading: true, error: null });
try {
const items = await api.getItems();
set({ items, isLoading: false });
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(items));
} catch (error) {
const cached = await AsyncStorage.getItem(STORAGE_KEY);
if (cached) {
set({ items: JSON.parse(cached), isLoading: false });
} else {
set({ error: 'Failed to fetch', isLoading: false });
}
}
},
// Optimistic update pattern
addItem: async (input) => {
const original = get().items;
const optimistic = { ...input, id: 'temp-' + Date.now() };
set((state) => ({ items: [...state.items, optimistic] }));
try {
const created = await api.createItem(input);
set((state) => ({
items: state.items.map((i) => (i.id === optimistic.id ? created : i)),
}));
} catch (error) {
set({ items: original, error: 'Failed to add' });
}
},
}));
Component Patterns
Screen Component
import { useEffect } from 'react';
import { SafeAreaView, StyleSheet } from 'react-native';
import { Stack } from 'expo-router';
import { useAppStore } from '../store';
import { colors } from '../theme';
export default function HomeScreen() {
const { items, isLoading, fetchItems } = useAppStore();
useEffect(() => { fetchItems(); }, []);
return (
<SafeAreaView style={styles.container}>
<Stack.Screen options={{ title: 'Home' }} />
<ItemList items={items} isLoading={isLoading} onRefresh={fetchItems} />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.background },
});
List Component with Pull-to-Refresh
import { FlatList, RefreshControl } from 'react-native';
import { colors } from '../theme';
interface ItemListProps {
items: Item[];
isLoading: boolean;
onRefresh: () => void;
}
export function ItemList({ items, isLoading, onRefresh }: ItemListProps) {
return (
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <ListItem item={item} />}
refreshControl={
<RefreshControl
refreshing={isLoading}
onRefresh={onRefresh}
tintColor={colors.primary}
/>
}
/>
);
}
Pressable Component with Accessibility
import { Pressable, Text, StyleSheet } from 'react-native';
export function ListItem({ item, onPress }: ListItemProps) {
return (
<Pressable
style={({ pressed }) => [styles.container, pressed && styles.pressed]}
onPress={() => onPress(item.id)}
accessibilityRole="button"
accessibilityLabel={item.title}
>
<Text style={styles.title}>{item.title}</Text>
</Pressable>
);
}
Navigation with Expo Router
File-Based Routing
app/
├── _layout.tsx # Root layout (Stack navigator)
├── index.tsx # Home screen (/)
├── [id].tsx # Dynamic route (/123)
└── (tabs)/ # Tab group
├── _layout.tsx # Tab navigator
├── index.tsx # First tab
└── settings.tsx # Settings tab
Navigation Actions
import { useRouter, useLocalSearchParams } from 'expo-router';
const router = useRouter();
const { id } = useLocalSearchParams<{ id: string }>();
router.push('/details');
router.push({ pathname: '/item/[id]', params: { id: '123' } });
router.back();
router.replace('/home');
Platform-Specific Code
Conditional Imports (CRITICAL)
Never use runtime require() for native modules — Metro still bundles them.
Use platform-specific files instead:
// BAD - Metro bundles both branches
if (Platform.OS !== 'web') {
const { Something } = require('native-module');
}
// GOOD - Use platform files
Component.tsx // Web fallback
Component.native.tsx // Native with imports
File Extensions
component.tsx # Shared code
component.ios.tsx # iOS only
component.android.tsx # Android only
component.web.tsx # Web only
component.native.tsx # iOS + Android
Common Commands
pnpm start # Start Expo dev server
pnpm ios # Run on iOS simulator
pnpm android # Run on Android emulator
pnpm web # Run in browser
Component Checklist
- Use StyleSheet.create (not inline styles)
- Add accessibility props (role, label, state)
- Handle loading and error states
- Support pull-to-refresh for lists
- Use KeyboardAvoidingView for forms
- Test on both iOS and Android
Performance Checklist
- Use FlatList for long lists (not ScrollView)
- Memoize expensive components with React.memo
- Use useCallback for event handlers passed as props
- Avoid inline styles in render
- Optimize images (proper sizing, caching)
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?