← スキル一覧に戻る

mobile-engineer
by ugudlado
Full-stack TypeScript monorepo template with Express API, React web, and Expo mobile apps sharing a common schema.
⭐ 0🍴 0📅 2026年1月6日
SKILL.md
name: mobile-engineer description: Expert in Template Project mobile 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.
Template Mobile Engineer
Expert knowledge of mobile patterns and architecture for the Template Project using Expo and React Native.
Core Architecture
Technology Stack
- Framework: Expo SDK 54 with React Native 0.81
- 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
CRITICAL: Where to Look Before Making Changes
Pattern References (ALWAYS CHECK THESE FIRST)
| Pattern | Primary Reference | Notes |
|---|---|---|
| Screen Layout | app/index.tsx | Main screen with list |
| Navigation Setup | app/_layout.tsx | Stack navigator config |
| Zustand Store | store/todoStore.ts | State with offline caching |
| API Service | services/api.ts | Type-safe API calls |
| List Component | components/TodoList.tsx | FlatList with pull-to-refresh |
| Item Component | components/TodoItem.tsx | Pressable with gestures |
| Form Component | components/AddTodoForm.tsx | Input with keyboard handling |
| Theme Colors | theme/colors.ts | Color palette + shadows |
| Types | types/todo.ts | Domain types |
Project Structure
packages/mobile/
├── app/ # Expo Router screens (file-based routing)
│ ├── _layout.tsx # Root layout with navigation config
│ └── index.tsx # Home screen
├── components/ # Reusable UI components
│ ├── TodoItem.tsx # Single todo item
│ ├── TodoList.tsx # List with pull-to-refresh
│ └── AddTodoForm.tsx # Form for new todos
├── store/ # Zustand state management
│ └── todoStore.ts # Todo state + API integration
├── services/ # API services
│ └── api.ts # Backend API client
├── theme/ # Design system
│ └── colors.ts # Colors, shadows, elevation
├── types/ # TypeScript types
│ └── todo.ts # Domain 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';
import { api } from '../services/api';
interface TodoState {
todos: Todo[];
isLoading: boolean;
error: string | null;
fetchTodos: () => Promise<void>;
addTodo: (input: CreateTodoInput) => Promise<void>;
toggleTodo: (id: string) => Promise<void>;
deleteTodo: (id: string) => Promise<void>;
}
const STORAGE_KEY = '@todos/data';
export const useTodoStore = create<TodoState>((set, get) => ({
todos: [],
isLoading: false,
error: null,
fetchTodos: async () => {
set({ isLoading: true, error: null });
try {
const todos = await api.getTodos();
set({ todos, isLoading: false });
// Cache for offline use
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
} catch (error) {
// Load from cache on error
const cached = await AsyncStorage.getItem(STORAGE_KEY);
if (cached) {
set({ todos: JSON.parse(cached), isLoading: false });
} else {
set({ error: 'Failed to fetch', isLoading: false });
}
}
},
// Optimistic update pattern
toggleTodo: async (id: string) => {
const originalTodos = get().todos;
// Optimistic update
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
}));
try {
await api.toggleTodo(id);
} catch (error) {
// Rollback on error
set({ todos: originalTodos, error: 'Failed to toggle' });
}
},
}));
Component Patterns
Screen Component
import React, { useEffect } from 'react';
import { View, SafeAreaView, StyleSheet } from 'react-native';
import { Stack } from 'expo-router';
import { useTodoStore } from '../store';
import { colors } from '../theme';
export default function HomeScreen() {
const { todos, isLoading, fetchTodos, toggleTodo } = useTodoStore();
useEffect(() => {
fetchTodos();
}, []);
return (
<SafeAreaView style={styles.container}>
<Stack.Screen options={{ title: 'My Todos' }} />
<TodoList
todos={todos}
isLoading={isLoading}
onRefresh={fetchTodos}
onToggle={toggleTodo}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.DEFAULT,
},
});
List Component with Pull-to-Refresh
import React from 'react';
import { FlatList, RefreshControl } from 'react-native';
import { TodoItem } from './TodoItem';
import { colors } from '../theme';
interface TodoListProps {
todos: Todo[];
isLoading: boolean;
onRefresh: () => void;
onToggle: (id: string) => void;
}
export function TodoList({ todos, isLoading, onRefresh, onToggle }: TodoListProps) {
return (
<FlatList
data={todos}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<TodoItem todo={item} onToggle={onToggle} />
)}
refreshControl={
<RefreshControl
refreshing={isLoading}
onRefresh={onRefresh}
tintColor={colors.primary.DEFAULT}
/>
}
/>
);
}
Pressable Component with Accessibility
import React from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
import { Check } from 'lucide-react-native';
import { colors, shadows } from '../theme';
interface TodoItemProps {
todo: Todo;
onToggle: (id: string) => void;
}
export function TodoItem({ todo, onToggle }: TodoItemProps) {
return (
<Pressable
style={({ pressed }) => [
styles.container,
shadows.sm,
pressed && styles.pressed,
]}
onPress={() => onToggle(todo.id)}
accessibilityRole="checkbox"
accessibilityState={{ checked: todo.completed }}
accessibilityLabel={todo.title}
>
<View style={[styles.checkbox, todo.completed && styles.checked]}>
{todo.completed && <Check size={14} color="#fff" />}
</View>
<Text style={[styles.title, todo.completed && styles.completed]}>
{todo.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
Stack Navigator Config
import { Stack } from 'expo-router';
import { colors } from '../theme';
export default function RootLayout() {
return (
<Stack
screenOptions={{
headerStyle: { backgroundColor: colors.background.DEFAULT },
headerTintColor: colors.text.primary,
contentStyle: { backgroundColor: colors.background.DEFAULT },
}}
/>
);
}
Navigation Actions
import { useRouter, useLocalSearchParams } from 'expo-router';
function MyComponent() {
const router = useRouter();
const { id } = useLocalSearchParams<{ id: string }>();
// Navigate
router.push('/details');
router.push({ pathname: '/todo/[id]', params: { id: '123' } });
// Go back
router.back();
// Replace (no back)
router.replace('/home');
}
Styling Patterns
StyleSheet Best Practices
import { StyleSheet, Platform } from 'react-native';
import { colors, shadows, elevation } from '../theme';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.DEFAULT,
},
card: {
backgroundColor: colors.background.card,
borderRadius: 12,
padding: 16,
// Platform-specific shadows
...Platform.select({
ios: shadows.md,
android: { elevation: elevation.md },
}),
},
text: {
fontSize: 16,
color: colors.text.primary,
fontWeight: '500',
},
});
Responsive Design
import { Dimensions, useWindowDimensions } from 'react-native';
function ResponsiveComponent() {
const { width, height } = useWindowDimensions();
const isTablet = width >= 768;
return (
<View style={[styles.container, isTablet && styles.tabletContainer]}>
{/* Content */}
</View>
);
}
API Integration
Service Pattern
const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:3000';
interface ApiResponse<T> {
success: boolean;
data: T;
}
async function request<T>(endpoint: string, options?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
headers: { 'Content-Type': 'application/json' },
...options,
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const json = await response.json();
return (json as ApiResponse<T>).data;
}
export const api = {
getTodos: () => request<Todo[]>('/api/todos'),
createTodo: (data: CreateTodoInput) =>
request<Todo>('/api/todos', {
method: 'POST',
body: JSON.stringify(data),
}),
};
Platform-Specific Code
Platform Select
import { Platform } from 'react-native';
// Inline
const padding = Platform.OS === 'ios' ? 20 : 16;
// Object select
const styles = Platform.select({
ios: { shadowOpacity: 0.2 },
android: { elevation: 4 },
default: {},
});
File Extensions
component.tsx # Shared code
component.ios.tsx # iOS only
component.android.tsx # Android only
component.web.tsx # Web only
Common Commands
# Navigate to package first
cd packages/mobile
# Development
pnpm start # Start Expo dev server
pnpm ios # Run on iOS simulator
pnpm android # Run on Android emulator
pnpm web # Run in browser
# Type checking
pnpm type-check # Run TypeScript checks
# Building
npx expo build:ios # Build for iOS
npx expo build:android # Build for Android
Quick Reference
Essential Hooks
// Navigation
useRouter() // Navigation actions
useLocalSearchParams() // Route params
usePathname() // Current path
// State
useTodoStore() // Zustand store
// React Native
useWindowDimensions() // Screen size
useColorScheme() // Light/dark mode
Component Checklist
- Use StyleSheet.create (not inline styles)
- Add accessibility props (role, label, state)
- Handle loading states
- Handle 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
- Avoid inline styles in render
- Optimize images (proper sizing, caching)
Critical Reminders
Before Any Development:
- Check existing components in
components/ - Check types in
types/ - Use Zustand for shared state, useState for local UI state
- Test on both iOS and Android
- Add proper accessibility props
Architecture Flow:
Screen -> Zustand Store -> API Service -> Backend
|
AsyncStorage (offline cache)
Remember: Mobile apps need offline support, proper loading states, and platform-specific handling. Always test on real devices before release.
スコア
総合スコア
60/100
リポジトリの品質指標に基づく評価
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
レビュー
💬
レビュー機能は近日公開予定です