Skip to main content

mobile

Mobile development with React Native, Flutter, and native patterns

Source facts

Repository
miles990/claude-software-skills
Last source activity
January 15, 2026 at 07:58
Detected SKILL.md language
English
Stars
25
Forks
6

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
5 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
mobile
description
Mobile development with React Native, Flutter, and native patterns
domain
development-stacks
version
1.0.0
tags
["react-native","flutter","ios","android","expo","mobile-ui"]
triggers
{"keywords":{"primary":["mobile","react native","flutter","ios","android","app","expo"],"secondary":["navigation","push notification","deep link","gesture","native module"]},"context_boost":["app store","play store","cross-platform","hybrid"],"context_penalty":["web","backend","desktop","server"],"priority":"high"}
# Mobile Development ## Overview Cross-platform and native mobile development patterns, frameworks, and best practices. --- ## React Native ### Component Patterns ```tsx // Functional component with hooks import React, { useState, useCallback } from 'react'; import { View, Text, FlatList, TouchableOpacity, StyleSheet, RefreshControl, } from 'react-native'; interface User { id: string; name: string; email: string; } interface UserListProps { users: User[]; onSelect: (user: User) => void; onRefresh: () => Promise<void>; } export function UserList({ users, onSelect, onRefresh }: UserListProps) { const [refreshing, setRefreshing] = useState(false); const handleRefresh = useCallback(async () => { setRefreshing(true); await onRefresh(); setRefreshing(false); }, [onRefresh]); const renderItem = useCallback(({ item }: { item: User }) => ( <TouchableOpacity style={styles.item} onPress={() => onSelect(item)} activeOpacity={0.7} > <Text style={styles.name}>{item.name}</Text> <Text style={styles.email}>{item.email}</Text> </TouchableOpacity> ), [onSelect]); const keyExtractor = useCallback((item: User) => item.id, []); return ( <FlatList data={users} renderItem={renderItem} keyExtractor={keyExtractor} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} /> } ItemSeparatorComponent={() => <View style={styles.separator} />} ListEmptyComponent={ <Text style={styles.empty}>No users found</Text> } /> ); } const styles = StyleSheet.create({ item: { padding: 16, backgroundColor: '#fff', }, name: { fontSize: 16, fontWeight: '600', color: '#1a1a1a', }, email: { fontSize: 14, color: '#666', marginTop: 4, }, separator: { height: 1, backgroundColor: '#e0e0e0', }, empty: { textAlign: 'center', padding: 32, color: '#999', }, }); ``` ### Navigation ```tsx // React Navigation setup import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; // Type-safe navigation type RootStackParamList = { Home: undefined; Profile: { userId: string }; Settings: undefined; }; type TabParamList = { Feed: undefined; Search: undefined; Notifications: undefined; Account: undefined; }; const Stack = createNativeStackNavigator<RootStackParamList>(); const Tab = createBottomTabNavigator<TabParamList>(); function TabNavigator() { return ( <Tab.Navigator screenOptions={({ route }) => ({ tabBarIcon: ({ focused, color, size }) => { const iconName = { Feed: focused ? 'home' : 'home-outline', Search: focused ? 'search' : 'search-outline', Notifications: focused ? 'bell' : 'bell-outline', Account: focused ? 'person' : 'person-outline', }[route.name]; return <Icon name={iconName} size={size} color={color} />; }, tabBarActiveTintColor: '#007AFF', tabBarInactiveTintColor: '#8E8E93', })} > <Tab.Screen name="Feed" component={FeedScreen} /> <Tab.Screen name="Search" component={SearchScreen} /> <Tab.Screen name="Notifications" component={NotificationsScreen} /> <Tab.Screen name="Account" component={AccountScreen} /> </Tab.Navigator> ); } function App() { return ( <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Home" component={TabNavigator} options={{ headerShown: false }} /> <Stack.Screen name="Profile" component={ProfileScreen} options={{ title: 'User Profile' }} /> <Stack.Screen name="Settings" component={SettingsScreen} /> </Stack.Navigator> </NavigationContainer> ); } // Using navigation in components import { useNavigation, useRoute } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; type ProfileScreenNavigationProp = NativeStackNavigationProp< RootStackParamList, 'Profile' >; function ProfileButton({ userId }: { userId: string }) { const navigation = useNavigation<ProfileScreenNavigationProp>(); return ( <Button title="View Profile" onPress={() => navigation.navigate('Profile', { userId })} /> ); } ``` ### State Management ```tsx // Zustand for React Native import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import AsyncStorage from '@react-native-async-storage/async-storage'; interface AuthState { user: User | null; token: string | null; login: (email: string, password: string) => Promise<void>; logout: () => void; } const useAuthStore = create<AuthState>()( persist( (set) => ({ user: null, token: null, login: async (email, password) => { const response = await api.login(email, password); set({ user: response.user, token: response.token }); }, logout: () => { set({ user: null, token: null }); }, }), { name: 'auth-storage', storage: createJSONStorage(() => AsyncStorage), } ) ); // React Query for data fetching import { useQuery, useMutation, QueryClient } from '@tanstack/react-query'; const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 2, staleTime: 5 * 60 * 1000, }, }, }); function usePosts() { return useQuery({ queryKey: ['posts'], queryFn: () => api.getPosts(), }); } function useCreatePost() { return useMutation({ mutationFn: (newPost: CreatePostInput) => api.createPost(newPost), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['posts'] }); }, }); } ``` ### Platform-Specific Code ```tsx import { Platform, StyleSheet } from 'react-native'; const styles = StyleSheet.create({ container: { paddingTop: Platform.OS === 'ios' ? 44 : 0, ...Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, android: { elevation: 4, }, }), }, }); // Platform-specific files // Button.ios.tsx // Button.android.tsx // Import as: import { Button } from './Button'; ``` --- ## Expo ### Expo Router ```tsx // app/_layout.tsx import { Stack } from 'expo-router'; export default function RootLayout() { return ( <Stack> <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack.Screen name="modal" options={{ presentation: 'modal' }} /> </Stack> ); } // app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; export default function TabLayout() { return ( <Tabs> <Tabs.Screen name="index" options={{ title: 'Home', tabBarIcon: ({ color }) => ( <Ionicons name="home" size={24} color={color} /> ), }} /> <Tabs.Screen name="profile" options={{ title: 'Profile', tabBarIcon: ({ color }) => ( <Ionicons name="person" size={24} color={color} /> ), }} /> </Tabs> ); } // app/(tabs)/index.tsx import { View, Text } from 'react-native'; import { Link } from 'expo-router'; export default function HomeScreen() { return ( <View> <Text>Home Screen</Text> <Link href="/profile">Go to Profile</Link> <Link href="/modal">Open Modal</Link> </View> ); } ``` ### Native APIs ```tsx import * as Camera from 'expo-camera'; import * as ImagePicker from 'expo-image-picker'; import * as Location from 'expo-location'; import * as Notifications from 'expo-notifications'; // Camera async function takePhoto() { const { status } = await Camera.requestCameraPermissionsAsync(); if (status !== 'granted') { Alert.alert('Permission required', 'Camera access is needed'); return; } const result = await ImagePicker.launchCameraAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.8, }); if (!result.canceled) { return result.assets[0].uri; } } // Location async function getCurrentLocation() { const { status } = await Location.requestForegroundPermissionsAsync(); if (status !== 'granted') { throw new Error('Location permission denied'); } const location = await Location.getCurrentPositionAsync({}); return { latitude: location.coords.latitude, longitude: location.coords.longitude, }; } // Push Notifications async function registerForPushNotifications() { const { status } = await Notifications.requestPermissionsAsync(); if (status !== 'granted') { return null; } const token = await Notifications.getExpoPushTokenAsync(); return token.data; } Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: true, }), }); ``` --- ## Flutter ### Widget Patterns ```dart // Stateless widget class UserCard extends StatelessWidget { final User user; final VoidCallback onTap; const UserCard({ super.key, required this.user, required this.onTap, }); @override Widget build(BuildContext context) { return Card( child: ListTile( leading: CircleAvatar( backgroundImage: NetworkImage(user.avatarUrl), ), title: Text(user.name), subtitle: Text(user.email), trailing: const Icon(Icons.chevron_right), onTap: onTap, ), ); } } // Stateful widget with hooks (flutter_hooks) class CounterPage extends HookWidget { @override Widget build(BuildContext context) { final count = useState(0); final controller = useAnimationController(duration: Duration(seconds: 1)); return Scaffold( body: Center( child: Text('Count: ${count.value}'), ), floatingActionButton: FloatingActionButton( onPressed: () => count.value++, child: const Icon(Icons.add), ), ); } } ```
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub